HeapProblem 10 of 18Important
Merge K Sorted Linked Lists [V.IMP]
Problem Statement
Given K sorted linked lists, merge them into a single sorted linked list.
Example:
- Input:
lists = [ 1 -> 4 -> 5, 1 -> 3 -> 4, 2 -> 6 ] - Output:
1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5 -> 6 - Explanation: All three sorted lists are merged into one sorted list.
Approach 1: Collect All Nodes and Sort (Brute Force)
Intuition
Collect all node values, sort them, and create a new linked list.
Algorithm
- Traverse all linked lists and collect values
- Sort the collected values
- Create a new linked list from sorted values
java
import java.util.*;
class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
}
public class Solution {
public ListNode mergeKLists(ListNode[] lists) {
List<Integer> values = new ArrayList<>();
// Collect all values
for (ListNode list : lists) {
while (list != null) {
values.add(list.val);
list = list.next;
}
}
// Sort values
Collections.sort(values);
// Create new linked list
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
for (int val : values) {
curr.next = new ListNode(val);
curr = curr.next;
}
return dummy.next;
}
}Complexity Analysis
- Time Complexity: O(NK log(NK)) - Where N is average list length, sorting takes O(NK log(NK))
- Space Complexity: O(N*K) - For storing all values
Approach 2: Merge Two at a Time
Intuition
Merge lists pairwise repeatedly until only one list remains.
Algorithm
- Merge first two lists
- Merge result with third list
- Continue until all lists are merged
java
class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
}
public class Solution {
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.next;
}
curr = curr.next;
}
curr.next = (l1 != null) ? l1 : l2;
return dummy.next;
}
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
ListNode result = lists[0];
for (int i = 1; i < lists.length; i++) {
result = mergeTwoLists(result, lists[i]);
}
return result;
}
}Complexity Analysis
- Time Complexity: O(K * N*K) - Each merge takes O(current length), total K-1 merges
- Space Complexity: O(1) - Merging in place
Approach 3: Divide and Conquer
Intuition
Merge lists in pairs like merge sort. This reduces the number of comparisons significantly.
Algorithm
- Pair up K lists and merge each pair
- Repeat until only one list remains
- Total log K levels of merging
java
class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
}
public class Solution {
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.next;
}
curr = curr.next;
}
curr.next = (l1 != null) ? l1 : l2;
return dummy.next;
}
private ListNode mergeKListsHelper(ListNode[] lists, int left, int right) {
if (left == right) {
return lists[left];
}
if (left > right) {
return null;
}
int mid = left + (right - left) / 2;
ListNode leftMerged = mergeKListsHelper(lists, left, mid);
ListNode rightMerged = mergeKListsHelper(lists, mid + 1, right);
return mergeTwoLists(leftMerged, rightMerged);
}
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
return mergeKListsHelper(lists, 0, lists.length - 1);
}
}Complexity Analysis
- Time Complexity: O(NK log K) - log K levels, each level processes all NK elements
- Space Complexity: O(log K) - Recursion stack
Approach 4: Min-Heap (Optimal)
Intuition
Use a min-heap to track the smallest node among the current heads of all lists. Always extract the minimum and advance that list's pointer.
Algorithm
- Add head of each list to min-heap
- Extract minimum node, add to result
- If extracted node has next, add next to heap
- Repeat until heap is empty
java
import java.util.*;
class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
}
public class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
// Min-heap based on node value
PriorityQueue<ListNode> minHeap = new PriorityQueue<>(
(a, b) -> a.val - b.val
);
// Add head of each list
for (ListNode list : lists) {
if (list != null) {
minHeap.offer(list);
}
}
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (!minHeap.isEmpty()) {
// Extract minimum
ListNode minNode = minHeap.poll();
curr.next = minNode;
curr = curr.next;
// Add next node from same list
if (minNode.next != null) {
minHeap.offer(minNode.next);
}
}
return dummy.next;
}
}Complexity Analysis
- Time Complexity: O(NK log K) - Each of NK nodes is pushed/popped once, each operation is O(log K)
- Space Complexity: O(K) - Heap stores at most K nodes (one from each list)
Key Takeaways
- Brute force ignores the sorted property - inefficient
- Sequential merging leads to O(K * N*K) - repeated work on same elements
- Divide and conquer achieves O(N*K log K) with O(log K) space
- Min-heap is optimal for space - only O(K) extra space
- This is LeetCode 23 - a very important interview problem
- Heap approach processes each node exactly once
- Same technique applies to merging K sorted arrays
- For Python, use tuple with index to break ties in heap comparison