Merge Two Sorted Lists
LeetCode Problem #21 | Difficulty: Easy | Topic: Linked List/Recursion
🔗 Problem Link: Merge Two Sorted Lists - LeetCode
Problem Statement
You are given the heads of two sorted linked lists list1 and list2.
Merge the two lists into one sorted list. The list should be made by splicing together the nodes of the first two lists.
Return the head of the merged linked list.
Example 1:
Input: list1 = [1,2,4], list2 = [1,3,4]
Output: [1,1,2,3,4,4]
Visual representation:
list1: 1 → 2 → 4
list2: 1 → 3 → 4
result: 1 → 1 → 2 → 3 → 4 → 4
Example 2:
Input: list1 = [], list2 = []
Output: []
Visual representation:
list1: null
list2: null
result: null
Example 3:
Input: list1 = [], list2 = [0]
Output: [0]
Visual representation:
list1: null
list2: 0
result: 0
Constraints:
- The number of nodes in both lists is in the range
[0, 50] -100 <= Node.val <= 100- Both
list1andlist2are sorted in non-decreasing order
🚀 Quick Revision Notes (1-Minute Read)
🎯 Optimal Approach: Iterative with Dummy Head | Time: O(m+n) | Space: O(1)
💡 Key Insight: Use a dummy head to simplify edge cases and maintain a pointer to build the result list.
🔥 Optimization Strategy:
- Dummy Head: Start with dummy node to avoid null checks
- Two Pointers: Compare values and link smaller node
- Append Remaining: Link remaining nodes from non-empty list
- Return: Return dummy.next as the actual head
⚡ Quick Implementation:
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(0);
ListNode current = dummy;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
current.next = list1;
list1 = list1.next;
} else {
current.next = list2;
list2 = list2.next;
}
current = current.next;
}
// Append remaining nodes
current.next = (list1 != null) ? list1 : list2;
return dummy.next;
}
🎪 Step-by-Step Pattern:
list1: 1 → 2 → 4
list2: 1 → 3 → 4
Step 1: Compare 1 vs 1 → choose list1(1), advance list1
Step 2: Compare 2 vs 1 → choose list2(1), advance list2
Step 3: Compare 2 vs 3 → choose list1(2), advance list1
Step 4: Compare 4 vs 3 → choose list2(3), advance list2
Step 5: Compare 4 vs 4 → choose list1(4), advance list1
Step 6: list1 empty → append remaining list2(4)
Result: 1 → 1 → 2 → 3 → 4 → 4
🚫 Common Mistakes:
- Forgetting to handle null input lists
- Not advancing pointers correctly
- Missing the remaining nodes after one list is exhausted
- Complex null checks without dummy head
📝 Pattern Recognition: Classic "merge pattern" fundamental for merge sort and k-way merge algorithms.
Understanding the Problem
Key Points:
- Input Constraint: Both lists are already sorted in non-decreasing order
- Merge Operation: Combine two lists while maintaining sorted order
- Node Reuse: Splice existing nodes, don't create new ones
- Edge Cases: Handle empty lists gracefully
What Makes This Fundamental:
- Linked List Manipulation: Core pointer operations
- Merge Algorithm: Foundation for merge sort
- Two Pointers Technique: Essential algorithmic pattern
- Recursion vs Iteration: Can be solved both ways
Visual Understanding:
Merge Process Visualization:
Initial State:
list1: 1 → 2 → 4 → null
list2: 1 → 3 → 4 → null
result: dummy → null
Step-by-step merge:
Compare 1 vs 1: take list1
result: dummy → 1
list1: 2 → 4 → null
Compare 2 vs 1: take list2
result: dummy → 1 → 1
list2: 3 → 4 → null
Compare 2 vs 3: take list1
result: dummy → 1 → 1 → 2
list1: 4 → null
Compare 4 vs 3: take list2
result: dummy → 1 → 1 → 2 → 3
list2: 4 → null
Compare 4 vs 4: take list1
result: dummy → 1 → 1 → 2 → 3 → 4
list1: null
Append remaining list2:
result: dummy → 1 → 1 → 2 → 3 → 4 → 4
The Core Challenge:
Problem: Merge two sorted linked lists efficiently
Key considerations:
1. Maintain sorted order
2. Handle null pointers
3. Reuse existing nodes
4. Minimize space usage
Two main approaches:
1. Iterative: Use dummy head and two pointers
2. Recursive: Natural merge logic with recursion
Approaches
Approach 1: Iterative with Dummy Head (Recommended)
Idea: Use a dummy head node to simplify the merging logic.
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(0);
ListNode current = dummy;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
current.next = list1;
list1 = list1.next;
} else {
current.next = list2;
list2 = list2.next;
}
current = current.next;
}
// Append remaining nodes
current.next = (list1 != null) ? list1 : list2;
return dummy.next;
}
Detailed Explanation of Iterative Approach
Core Concept: Use a dummy node to avoid complex null checks and maintain a current pointer to build the result.
Step-by-Step Process:
Example: list1 = [1,2,4], list2 = [1,3,4]
Initial:
dummy → null
current = dummy
list1 → 1 → 2 → 4 → null
list2 → 1 → 3 → 4 → null
Iteration 1: Compare 1 vs 1
- list1.val (1) <= list2.val (1) ✓
- current.next = list1 → dummy → 1
- list1 = list1.next → list1 → 2 → 4 → null
- current = current.next → current = 1
Iteration 2: Compare 2 vs 1
- list1.val (2) <= list2.val (1) ✗
- current.next = list2 → dummy → 1 → 1
- list2 = list2.next → list2 → 3 → 4 → null
- current = current.next → current = 1 (second one)
Iteration 3: Compare 2 vs 3
- list1.val (2) <= list2.val (3) ✓
- current.next = list1 → dummy → 1 → 1 → 2
- list1 = list1.next → list1 → 4 → null
- current = current.next → current = 2
Iteration 4: Compare 4 vs 3
- list1.val (4) <= list2.val (3) ✗
- current.next = list2 → dummy → 1 → 1 → 2 → 3
- list2 = list2.next → list2 → 4 → null
- current = current.next → current = 3
Iteration 5: Compare 4 vs 4
- list1.val (4) <= list2.val (4) ✓
- current.next = list1 → dummy → 1 → 1 → 2 → 3 → 4
- list1 = list1.next → list1 = null
- current = current.next → current = 4
End loop (list1 is null):
- current.next = list2 → dummy → 1 → 1 → 2 → 3 → 4 → 4
- Return dummy.next → 1 → 1 → 2 → 3 → 4 → 4
Time Complexity: O(m + n) - visit each node exactly once
Space Complexity: O(1) - only constant extra space
Approach 2: Recursion
Idea: Use recursive calls to naturally handle the merge logic.
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// Base cases
if (list1 == null) return list2;
if (list2 == null) return list1;
// Recursive case
if (list1.val <= list2.val) {
list1.next = mergeTwoLists(list1.next, list2);
return list1;
} else {
list2.next = mergeTwoLists(list1, list2.next);
return list2;
}
}
Detailed Explanation of Recursive Approach
Core Concept: At each step, choose the smaller head and recursively merge the remaining lists.
Recursion Tree for [1,2,4] and [1,3,4]:
mergeTwoLists([1,2,4], [1,3,4])
├── 1 <= 1 ✓, choose list1[1]
└── mergeTwoLists([2,4], [1,3,4])
├── 2 <= 1 ✗, choose list2[1]
└── mergeTwoLists([2,4], [3,4])
├── 2 <= 3 ✓, choose list1[2]
└── mergeTwoLists([4], [3,4])
├── 4 <= 3 ✗, choose list2[3]
└── mergeTwoLists([4], [4])
├── 4 <= 4 ✓, choose list1[4]
└── mergeTwoLists(null, [4])
└── return [4]
Result builds up: 1 → 1 → 2 → 3 → 4 → 4
Time Complexity: O(m + n) - each node processed once
Space Complexity: O(m + n) - recursion call stack
Approach 3: Iterative without Dummy Head
Idea: Handle the first node separately, then proceed with the merge.
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
ListNode head, current;
// Initialize head
if (list1.val <= list2.val) {
head = current = list1;
list1 = list1.next;
} else {
head = current = list2;
list2 = list2.next;
}
// Merge remaining
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
current.next = list1;
list1 = list1.next;
} else {
current.next = list2;
list2 = list2.next;
}
current = current.next;
}
// Append remaining
current.next = (list1 != null) ? list1 : list2;
return head;
}
Detailed Explanation of No Dummy Head Approach
Core Concept: Explicitly handle the head initialization to avoid using a dummy node.
Why More Complex:
Without dummy head, we need to:
1. Handle null inputs first
2. Determine which list provides the head
3. Initialize both head and current pointers
4. Then proceed with normal merge logic
With dummy head:
1. Create dummy node
2. Start merge loop immediately
3. Return dummy.next
Dummy head approach is cleaner and less error-prone
Time Complexity: O(m + n) - same merge process
Space Complexity: O(1) - no extra space except pointers
Edge Cases and Testing
Critical Edge Cases:
-
Both Lists Empty:
Input: list1 = null, list2 = null Output: null -
One List Empty:
Input: list1 = [1,2,3], list2 = null Output: [1,2,3] Input: list1 = null, list2 = [1,2,3] Output: [1,2,3] -
Equal Elements:
Input: list1 = [1,1,1], list2 = [1,1,1] Output: [1,1,1,1,1,1] -
No Overlap:
Input: list1 = [1,2,3], list2 = [4,5,6] Output: [1,2,3,4,5,6] -
Different Lengths:
Input: list1 = [1], list2 = [1,3,4,5,6] Output: [1,1,3,4,5,6]
Test Cases for Validation:
// Edge cases
mergeTwoLists(null, null) → null
mergeTwoLists([1,2,3], null) → [1,2,3]
mergeTwoLists(null, [1,2,3]) → [1,2,3]
// Basic cases
mergeTwoLists([1,2,4], [1,3,4]) → [1,1,2,3,4,4]
mergeTwoLists([1], [2]) → [1,2]
mergeTwoLists([2], [1]) → [1,2]
// Equal elements
mergeTwoLists([1,1], [1,1]) → [1,1,1,1]
// Different lengths
mergeTwoLists([1,2], [3,4,5,6,7]) → [1,2,3,4,5,6,7]
mergeTwoLists([5,6,7], [1,2,3,4]) → [1,2,3,4,5,6,7]
// No overlap
mergeTwoLists([1,3,5], [2,4,6]) → [1,2,3,4,5,6]
Key Insights and Patterns
Why This Problem is Fundamental:
- Merge Pattern: Foundation for merge sort algorithm
- Linked List Manipulation: Essential pointer techniques
- Two Pointers: Classic algorithmic pattern
- Recursion: Natural recursive structure
Common Applications:
- Merge Sort: Core subroutine for divide-and-conquer sorting
- K-way Merge: Extends to merging multiple sorted lists
- Database Operations: Merging sorted result sets
- Stream Processing: Combining sorted data streams
Related LeetCode Problems:
- #23 Merge k Sorted Lists (Hard) - Extension to multiple lists
- #88 Merge Sorted Array (Easy) - Array version of merge
- #148 Sort List (Medium) - Merge sort for linked lists
- #1634 Add Two Polynomials (Medium) - Similar merge logic
Templates and Quick Reference
Iterative Template (Recommended):
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(0);
ListNode current = dummy;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
current.next = list1;
list1 = list1.next;
} else {
current.next = list2;
list2 = list2.next;
}
current = current.next;
}
current.next = (list1 != null) ? list1 : list2;
return dummy.next;
}
Recursive Template:
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
if (list1.val <= list2.val) {
list1.next = mergeTwoLists(list1.next, list2);
return list1;
} else {
list2.next = mergeTwoLists(list1, list2.next);
return list2;
}
}
Memory Aid:
Merge Two Sorted Lists Pattern:
1. DUMMY HEAD: Simplifies edge cases
2. TWO POINTERS: Compare and advance smaller value
3. APPEND REMAINING: Link leftover nodes
4. RETURN DUMMY.NEXT: Skip dummy node
Visual Memory:
list1: 1 → 2 → 4
list2: 1 → 3 → 4
↓ ↓ ↓
Compare → Link → Advance
Remember: "Choose smaller, link, advance, repeat!"