21. Merge Two Sorted Lists
Problem Description
You need to merge two sorted linked lists into a single sorted linked list.
Given two linked lists list1
and list2
, where each list is already sorted in ascending order, your task is to combine them into one linked list that maintains the sorted order.
The merging should be done by splicing the existing nodes together - meaning you should reuse the nodes from the original lists rather than creating new nodes. You simply need to rearrange the next
pointers to connect the nodes in the correct order.
For example:
- If
list1
is1 -> 2 -> 4
andlist2
is1 -> 3 -> 4
- The merged result should be
1 -> 1 -> 2 -> 3 -> 4 -> 4
The function should return the head node of the newly merged linked list.
The solution uses a recursive approach:
- If either list is empty, return the other list (base case)
- Compare the values of the current heads of both lists
- Choose the node with the smaller value as the next node in the merged list
- Recursively merge the remainder of the chosen list with the other list
- Connect the chosen node to the result of the recursive call
This approach ensures that at each step, we're always selecting the smaller value between the two current heads, maintaining the sorted order throughout the merging process.
Intuition
When merging two sorted lists, we need to repeatedly make a simple decision: which element should come next in our merged list? Since both lists are already sorted, we know the smallest remaining element must be one of the two current heads.
Think of it like merging two sorted piles of cards face-up. You can only see the top card of each pile, and you need to build a new sorted pile. The strategy is straightforward - always take the smaller top card and add it to your result pile.
The recursive nature becomes clear when we realize that after choosing one node, we're left with the same problem but slightly smaller - we still need to merge two sorted lists, just with one list having one fewer element. This self-similar structure naturally leads to recursion.
The key insight is that once we decide which node comes first (the one with the smaller value), the rest of the merged list is simply the result of merging:
- The remainder of the chosen list (everything after the head we just picked)
- The entirety of the other list (which still has its original head)
For example, if we have list1 = [1, 3, 5]
and list2 = [2, 4, 6]
:
- We compare
1
and2
, choose1
- Now we need to merge
[3, 5]
with[2, 4, 6]
- This is the exact same problem, just smaller!
The base case emerges naturally: when one list becomes empty, there's nothing left to compare - we simply return the non-empty list as it's already sorted.
By connecting each chosen node to the result of merging the remaining elements, we build our final sorted list from the bottom up as the recursive calls return.
Learn more about Recursion and Linked List patterns.
Solution Approach
The solution implements a recursive approach to merge two sorted linked lists. Let's walk through the implementation step by step:
Base Case Handling:
if list1 is None or list2 is None: return list1 or list2
When either list1
or list2
is empty (None
), we return the non-empty list. The expression list1 or list2
elegantly returns list1
if it's not None
, otherwise returns list2
. This handles three scenarios:
- If
list1
isNone
, returnlist2
- If
list2
isNone
, returnlist1
- If both are
None
, returnNone
Recursive Merging: The core logic compares the values at the heads of both lists:
if list1.val <= list2.val:
list1.next = self.mergeTwoLists(list1.next, list2)
return list1
When list1
's head value is smaller or equal:
- We choose
list1
's head as the next node in our merged list - Recursively merge
list1.next
(rest of list1) with the entirelist2
- Connect the result of the recursive call to
list1.next
- Return
list1
as the head of the merged portion
else:
list2.next = self.mergeTwoLists(list1, list2.next)
return list2
When list2
's head value is smaller:
- We choose
list2
's head as the next node - Recursively merge the entire
list1
withlist2.next
(rest of list2) - Connect the result to
list2.next
- Return
list2
as the head of the merged portion
How the Recursion Builds the Result: The recursion works from the bottom up. Each recursive call:
- Selects the smaller head node
- Makes a recursive call to merge the remaining nodes
- Connects the selected node to the result of the recursive call
- Returns the selected node
As the recursion unwinds, it builds the complete merged list by connecting nodes in sorted order. The beauty of this approach is that we're reusing the existing nodes and only modifying their next
pointers to create the merged structure.
Time and Space Complexity:
- Time:
O(m + n)
wherem
andn
are the lengths of the two lists, as we visit each node exactly once - Space:
O(m + n)
for the recursion call stack in the worst case
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through merging list1 = 1 -> 3 -> 5
and list2 = 2 -> 4
step by step.
Initial Call: mergeTwoLists([1->3->5], [2->4])
- Compare heads:
1 < 2
- Choose node with value
1
from list1 - Make recursive call:
mergeTwoLists([3->5], [2->4])
Second Call: mergeTwoLists([3->5], [2->4])
- Compare heads:
3 > 2
- Choose node with value
2
from list2 - Make recursive call:
mergeTwoLists([3->5], [4])
Third Call: mergeTwoLists([3->5], [4])
- Compare heads:
3 < 4
- Choose node with value
3
from list1 - Make recursive call:
mergeTwoLists([5], [4])
Fourth Call: mergeTwoLists([5], [4])
- Compare heads:
5 > 4
- Choose node with value
4
from list2 - Make recursive call:
mergeTwoLists([5], [])
Fifth Call: mergeTwoLists([5], [])
- list2 is empty (base case)
- Return list1:
[5]
Unwinding the Recursion:
As the recursive calls return, connections are made:
- Fourth call returns: Node
4
connects to[5]
, becoming4 -> 5
- Third call returns: Node
3
connects to[4->5]
, becoming3 -> 4 -> 5
- Second call returns: Node
2
connects to[3->4->5]
, becoming2 -> 3 -> 4 -> 5
- Initial call returns: Node
1
connects to[2->3->4->5]
, becoming1 -> 2 -> 3 -> 4 -> 5
Final Result: 1 -> 2 -> 3 -> 4 -> 5
The key insight is that each recursive call selects the smaller head, delegates the remaining merge to a recursive call, and then connects the selected node to the result. This builds the merged list from bottom to top as the recursion unwinds.
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7from typing import Optional
8
9
10class Solution:
11 def mergeTwoLists(
12 self, list1: Optional[ListNode], list2: Optional[ListNode]
13 ) -> Optional[ListNode]:
14 """
15 Merge two sorted linked lists and return the head of the merged sorted list.
16
17 Args:
18 list1: Head of the first sorted linked list
19 list2: Head of the second sorted linked list
20
21 Returns:
22 Head of the merged sorted linked list
23 """
24 # Base case: if either list is empty, return the other list
25 if list1 is None or list2 is None:
26 return list1 or list2
27
28 # Compare the values of the current nodes
29 if list1.val <= list2.val:
30 # If list1's value is smaller or equal, use list1's node
31 # Recursively merge the rest of list1 with list2
32 list1.next = self.mergeTwoLists(list1.next, list2)
33 return list1
34 else:
35 # If list2's value is smaller, use list2's node
36 # Recursively merge list1 with the rest of list2
37 list2.next = self.mergeTwoLists(list1, list2.next)
38 return list2
39
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 /**
13 * Merges two sorted linked lists into one sorted linked list.
14 * The merge is done recursively by comparing the values of the current nodes.
15 *
16 * @param list1 The head of the first sorted linked list
17 * @param list2 The head of the second sorted linked list
18 * @return The head of the merged sorted linked list
19 */
20 public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
21 // Base case: if list1 is empty, return list2
22 if (list1 == null) {
23 return list2;
24 }
25
26 // Base case: if list2 is empty, return list1
27 if (list2 == null) {
28 return list1;
29 }
30
31 // Compare the values of the current nodes
32 if (list1.val <= list2.val) {
33 // If list1's value is smaller or equal, choose list1's node
34 // Recursively merge the rest of list1 with list2
35 list1.next = mergeTwoLists(list1.next, list2);
36 return list1;
37 } else {
38 // If list2's value is smaller, choose list2's node
39 // Recursively merge list1 with the rest of list2
40 list2.next = mergeTwoLists(list1, list2.next);
41 return list2;
42 }
43 }
44}
45
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 /**
14 * Merges two sorted linked lists into one sorted linked list
15 * @param list1 - pointer to the head of the first sorted linked list
16 * @param list2 - pointer to the head of the second sorted linked list
17 * @return pointer to the head of the merged sorted linked list
18 */
19 ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
20 // Base case: if list1 is empty, return list2
21 if (list1 == nullptr) {
22 return list2;
23 }
24
25 // Base case: if list2 is empty, return list1
26 if (list2 == nullptr) {
27 return list1;
28 }
29
30 // Compare the values of the current nodes
31 if (list1->val <= list2->val) {
32 // If list1's value is smaller or equal, choose list1's node
33 // Recursively merge the rest of list1 with list2
34 list1->next = mergeTwoLists(list1->next, list2);
35 return list1;
36 } else {
37 // If list2's value is smaller, choose list2's node
38 // Recursively merge list1 with the rest of list2
39 list2->next = mergeTwoLists(list1, list2->next);
40 return list2;
41 }
42 }
43};
44
1/**
2 * Definition for singly-linked list node.
3 * Each node contains a value and a reference to the next node.
4 */
5class ListNode {
6 val: number;
7 next: ListNode | null;
8
9 constructor(val?: number, next?: ListNode | null) {
10 this.val = (val === undefined ? 0 : val);
11 this.next = (next === undefined ? null : next);
12 }
13}
14
15/**
16 * Merges two sorted linked lists into a single sorted linked list.
17 * The merge is done in-place by adjusting the node pointers.
18 *
19 * @param list1 - The head of the first sorted linked list
20 * @param list2 - The head of the second sorted linked list
21 * @returns The head of the merged sorted linked list
22 */
23function mergeTwoLists(list1: ListNode | null, list2: ListNode | null): ListNode | null {
24 // Base case: if either list is empty, return the other list
25 if (list1 === null || list2 === null) {
26 return list1 || list2;
27 }
28
29 // Compare the values of the current nodes
30 if (list1.val < list2.val) {
31 // If list1's value is smaller, it becomes the current node
32 // Recursively merge the rest of list1 with list2
33 list1.next = mergeTwoLists(list1.next, list2);
34 return list1;
35 } else {
36 // If list2's value is smaller or equal, it becomes the current node
37 // Recursively merge list1 with the rest of list2
38 list2.next = mergeTwoLists(list1, list2.next);
39 return list2;
40 }
41}
42
Time and Space Complexity
Time Complexity: O(m + n)
The recursive solution visits each node from both linked lists exactly once. In each recursive call, we process one node from either list1
or list2
and make a recursive call with the remaining nodes. Since we have m
nodes in list1
and n
nodes in list2
, the total number of recursive calls equals m + n
. Each recursive call performs constant time operations (comparison and pointer assignment), resulting in an overall time complexity of O(m + n)
.
Space Complexity: O(m + n)
The space complexity is determined by the recursive call stack. In the worst case, we need to make m + n
recursive calls before reaching the base case (when one or both lists become None
). Each recursive call adds a new frame to the call stack, consuming O(1)
space. Therefore, the maximum depth of the recursion is m + n
, leading to a space complexity of O(m + n)
. This represents the implicit space used by the recursion stack rather than explicit auxiliary space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Stack Overflow with Very Long Lists
The recursive solution can cause a stack overflow when merging extremely long lists (typically lists with thousands of nodes), as each recursive call adds a frame to the call stack.
Why it happens: The recursion depth equals the total number of nodes in both lists. Python has a default recursion limit (usually around 1000), which can be exceeded with large inputs.
Solution: Use an iterative approach instead:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
# Create a dummy node to simplify the logic
dummy = ListNode(0)
current = dummy
# Iterate while both lists have nodes
while list1 and list2:
if list1.val <= list2.val:
current.next = list1
list1 = list1.next
else:
current.next = list2
list2 = list2.next
current = current.next
# Attach any remaining nodes
current.next = list1 or list2
return dummy.next
2. Modifying Input Lists Without Consideration
The recursive solution modifies the original linked lists by changing their next
pointers. This might be unexpected if the caller needs to preserve the original list structures.
Why it matters: After merging, the original lists are destroyed and cannot be used independently anymore.
Solution: If preserving original lists is required, create new nodes instead of reusing existing ones:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
if not list1 or not list2:
# Create copies of remaining nodes if needed
return self.copyList(list1 or list2)
if list1.val <= list2.val:
new_node = ListNode(list1.val)
new_node.next = self.mergeTwoLists(list1.next, list2)
return new_node
else:
new_node = ListNode(list2.val)
new_node.next = self.mergeTwoLists(list1, list2.next)
return new_node
3. Not Handling Equal Values Consistently
While the current solution correctly handles equal values by using <=
, beginners might use only <
, which could lead to infinite recursion in certain edge cases or incorrect ordering when custom comparison logic is involved.
Why it matters: Using strict inequality (<
) without proper else handling could cause issues with stability of the merge or create unexpected behavior.
Best Practice: Always use <=
to ensure deterministic behavior and maintain stability (elements from list1 come before equal elements from list2).
Which of the following is a min heap?
Recommended Readings
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!