Hello! In our previous lesson, we explored Floyd's Cycle-Finding Algorithm, a clever application of the slow-fast pointer technique for solving a complex problem in constant space. This pattern of using multiple pointers to traverse a list is a fundamental skill we'll continue to build upon.
Today, we'll tackle another essential linked list problem: merging two sorted linked lists into a single, comprehensive sorted list. This operation is not just a common interview question; it's also the core component of the Merge Sort algorithm when applied to linked lists. Following your preferred learning style, we will progress from a straightforward brute-force method to a more nuanced recursive solution, and finally to the optimal iterative approach that you would be expected to produce in a technical interview.
1. Brute-Force: The "Collect and Sort" Method
When first approaching this problem, the most direct solution is to leverage data structures you're already familiar with. Since the goal is a single sorted list, we can simply gather all the elements from both input lists, sort them, and then construct a new linked list from the sorted elements.
The steps for this approach are:
- Create a dynamic array, like a Java
ArrayList. - Traverse the first linked list (
list1) and add each node's value to the array. - Traverse the second linked list (
list2) and add each of its node's values to the same array. - Use a standard sorting function, such as
Collections.sort(), to sort the array. - Create a new linked list by iterating through the sorted array and creating a new node for each element.
The following video from the takeUforward channel explains this brute-force strategy clearly.
L23. Merge two sorted Linked Lists
This segment introduces the problem and details the brute-force solution, which involves using an auxiliary array. Pay attention to the analysis of why this approach, while simple, is not optimal.
Watch from the beginning until the complexity analysis of the brute-force solution. Striver walks through the logic of collecting, sorting, and rebuilding the list.
To see this implemented in Java, you can review the corresponding section in the takeUforward article.
Merge two sorted Linked Lists - Tutorial
This article section outlines the algorithm for the brute-force approach and provides a complete Java implementation.
Read the Algorithm section to understand the steps. Then, examine the full Java code to see how an ArrayList and Collections.sort() are used to achieve the result.
This method is functionally correct but has significant drawbacks in terms of efficiency, which an interviewer would immediately ask you to improve upon.
- Time Complexity: O(N log N), where N is the total number of nodes (N1 + N2). The dominant step is sorting the array.
- Space Complexity: O(N), as we need an array to store all N elements, plus the space for the newly created linked list.
The key inefficiency is that we are ignoring a critical piece of information: the input lists are already sorted. Our next approaches will exploit this property.
2. A Better Approach: Recursive Merging
A more elegant solution uses recursion, which often maps well to linked list problems due to their inherent recursive structure (a list is a head node followed by the rest of the list). The logic here follows a "divide and conquer" strategy.
The core idea is:
- Base Cases: If either of the lists is empty, the result of the merge is simply the other, non-empty list.
- Recursive Step: Compare the values at the heads of
list1andlist2.- If
list1.datais smaller, then this node is the head of our merged list. Itsnextpointer should be the result of recursively merging the rest oflist1(i.e.,list1.next) with the entirety oflist2. - Otherwise,
list2.datais smaller (or equal), so it becomes the head. Itsnextpointer is the result of recursively merginglist1with the rest oflist2(list2.next).
- If
This approach builds the final list by repeatedly picking the smaller of the two available nodes and letting recursion handle the rest. To visualize how the call stack manages this process, the following video provides an excellent, detailed walkthrough.
Merge Two Sorted Lists - LeetCode 21 - Java | Recursion explained
This video from LeetCode University offers a deep dive into the recursive solution, focusing on how the call stack works. This is a great way to solidify your understanding of recursion.
Watch the explanation of the recursive logic, and then follow the detailed, step-by-step call stack visualization. Finally, see how this translates directly into Java code.
The GeeksforGeeks article below also provides a clear algorithm and Java implementation for this recursive method.
This resource succinctly explains the recursive merge logic.
Focus on the section titled Algorithm, and then study the corresponding Java code. Notice how compact and expressive the recursive solution is.
This approach is much more efficient time-wise, but it has a hidden space cost.
- Time Complexity: O(N1 + N2). We visit each node in both lists exactly once.
- Space Complexity: O(N1 + N2). This is due to the recursion call stack. In the worst-case scenario (e.g., merging a list with one very large element and another list with many small elements), the recursion depth can be equal to the total number of nodes.
This leads us to the optimal solution, which achieves the same time complexity but with constant space.
3. Optimal Approach: Iterative Two-Pointer Merge
The standard and most efficient solution for this problem is an iterative one that uses two pointers and a dummy node. This approach avoids the overhead of the recursion stack and manipulates the next pointers of the existing nodes directly, achieving O(1) space complexity.
Here’s the process:
- Create a
dummynode (e.g., with a value of -1). This acts as a sentinel, providing a fixed starting point for our new list and simplifying the code by removing edge cases for an empty list. - Create a
tailpointer, initially pointing to thedummynode. Thistailpointer will always point to the last node of the merged list as we build it. - Use two pointers,
l1andl2, to traverse the input lists. - While both
l1andl2are notnull, compare their values:- Whichever node is smaller is appended to the merged list by setting
tail.nextto that node. - The pointer of the list from which the node was taken is advanced (e.g.,
l1 = l1.next). - The
tailpointer is advanced to the newly added node (tail = tail.next).
- Whichever node is smaller is appended to the merged list by setting
- When the loop terminates, one of the lists is exhausted. The other list may still contain nodes. Since they are already sorted, we can simply append the entire remaining portion by setting
tail.nextto the head of the non-empty list. - The head of the final merged list is
dummy.next.
This process is visualized perfectly in the following diagram.
The following video from takeUforward provides an excellent walkthrough of this optimal method.
L23. Merge two sorted Linked Lists
This video covers the optimal two-pointer approach, explaining the use of a dummy node and how to reuse existing nodes to achieve O(1) space complexity.
Watch the detailed explanation from the optimized approach section. Striver clearly explains the pointer manipulations and complexity analysis.
Finally, review the Java implementation in the takeUforward article to see the complete, production-ready code.
Merge two sorted Linked Lists - Tutorial
This article details the optimal iterative algorithm using a dummy node.
Read the Algorithm section for a step-by-step guide. Then, analyze the Java code and the final Complexity Analysis.
This iterative approach is the one you should aim for in an interview.
- Time Complexity: O(N1 + N2), as we traverse both lists once.
- Space Complexity: O(1), as we only use a few extra pointers and reuse the existing nodes.
Practice Problem
You are now ready to implement this on your own. This is a very common LeetCode problem. Try to code all three solutions: brute-force, recursive, and finally, the optimal iterative one.
Conclusion
In this lesson, we explored three different ways to merge two sorted linked lists, demonstrating a clear progression from a simple but inefficient solution to a highly optimized one. Understanding these trade-offs is crucial for interview success.
Key Takeaways:
- Brute-Force: Simple to conceptualize (collect, sort, rebuild) but inefficient with O(N log N) time and O(N) space.
- Recursive: An elegant solution that leverages the sorted property for O(N1 + N2) time but incurs O(N1 + N2) space complexity due to the call stack.
- Optimal (Iterative): The interview-standard solution. It uses a two-pointer technique and a
dummyhead node to merge the lists in O(N1 + N2) time and O(1) space by rewiring existing nodes.
In our next lesson, we will tackle another classic problem: determining if a linked list is a palindrome. This will challenge you to combine several techniques we've learned, including list reversal and the slow-fast pointer pattern, to achieve an optimal O(1) space solution.