Merge K Sorted Lists Using Min Heap in DSA Java - Time & Space Complexity
We want to understand how the time needed to merge multiple sorted lists grows as the number of lists and their sizes increase.
Specifically, how does using a min heap help manage this merging efficiently?
Analyze the time complexity of the following code snippet.
import java.util.*;
public class Solution {
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue heap = new PriorityQueue<>(Comparator.comparingInt(n -> n.val));
for (ListNode node : lists) {
if (node != null) heap.add(node);
}
ListNode dummy = new ListNode(0);
ListNode current = dummy;
while (!heap.isEmpty()) {
ListNode smallest = heap.poll();
current.next = smallest;
current = current.next;
if (smallest.next != null) heap.add(smallest.next);
}
return dummy.next;
}
}
This code merges k sorted linked lists into one sorted list using a min heap to always pick the smallest current node.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Removing the smallest node from the heap and adding the next node from that list.
- How many times: This happens once for every node across all lists, so total nodes = n.
Each node is pushed and popped from the heap once. The heap size is at most k (number of lists).
| Input Size (n) | Approx. Operations |
|---|---|
| 10 (nodes total) | About 10 insertions and 10 removals from a heap of size ≤ k |
| 100 | About 100 insertions and removals, each costing log k steps |
| 1000 | About 1000 insertions and removals, each costing log k steps |
Pattern observation: The total work grows linearly with the total number of nodes, multiplied by the cost of heap operations which grows slowly with the number of lists.
Time Complexity: O(n log k)
This means the time grows mostly with the total number of nodes, but each step involves a quick operation on a small heap of size k.
[X] Wrong: "Merging k lists is just like merging two lists, so it takes O(n) time."
[OK] Correct: Merging k lists involves managing multiple lists at once, so we must repeatedly find the smallest among k candidates, which adds a log k factor.
Understanding this time complexity helps you explain why using a min heap is efficient for merging multiple sorted lists, a common real-world problem.
"What if we used a simple linear search to find the smallest node instead of a min heap? How would the time complexity change?"
Practice
Solution
Step 1: Understand the role of min heap in merging
A min heap helps quickly find the smallest element among multiple sorted lists by always keeping the smallest element at the top.Step 2: Why min heap is used here
When merging K sorted lists, we repeatedly pick the smallest current element from all lists. The min heap efficiently supports this operation.Final Answer:
To efficiently get the smallest element among the heads of all lists -> Option AQuick Check:
Min heap = smallest element retrieval [OK]
- Thinking min heap sorts entire lists
- Using max heap instead of min heap
- Storing all elements at once in heap
Solution
Step 1: Check default behavior of PriorityQueue
By default, Java's PriorityQueue is a min heap, so new PriorityQueue<>() creates a min heap.Step 2: Analyze other options
Options A, B, and D create max heaps using reverse order or equivalent. Only C uses the default min heap.Final Answer:
PriorityQueue<Integer> minHeap = new PriorityQueue<>(); -> Option BQuick Check:
Default PriorityQueue = min heap [OK]
- Using reverseOrder() creates max heap, not min heap
- Confusing Comparator.naturalOrder() usage
- Not importing java.util.PriorityQueue
List<Integer> l1 = List.of(1, 4, 5);
List<Integer> l2 = List.of(1, 3, 4);
List<Integer> l3 = List.of(2, 6);
PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> a[0] - b[0]);
minHeap.offer(new int[]{l1.get(0), 0, 0});
minHeap.offer(new int[]{l2.get(0), 1, 0});
minHeap.offer(new int[]{l3.get(0), 2, 0});
List<Integer> result = new ArrayList<>();
while (!minHeap.isEmpty()) {
int[] curr = minHeap.poll();
result.add(curr[0]);
int listIndex = curr[1];
int elementIndex = curr[2];
if (elementIndex + 1 < (listIndex == 0 ? l1.size() : listIndex == 1 ? l2.size() : l3.size())) {
int nextVal = (listIndex == 0 ? l1 : listIndex == 1 ? l2 : l3).get(elementIndex + 1);
minHeap.offer(new int[]{nextVal, listIndex, elementIndex + 1});
}
}
System.out.println(result);Solution
Step 1: Initialize min heap with first elements
Heap starts with [1(l1), 1(l2), 2(l3)]. Poll smallest 1 from l1, add next 4 from l1. Then poll 1 from l2, add next 3 from l2, and so on.Step 2: Continue polling and adding next elements
Sequence of polled elements is 1(l1), 1(l2), 2(l3), 3(l2), 4(l1), 4(l2), 5(l1), 6(l3). This forms the merged sorted list.Final Answer:
[1, 1, 2, 3, 4, 4, 5, 6] -> Option DQuick Check:
Merged sorted list = [1,1,2,3,4,4,5,6] [OK]
- Forgetting to add next element after polling
- Mixing list indices when adding next elements
- Not handling empty lists properly
PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> b[0] - a[0]);
for (int i = 0; i < lists.size(); i++) {
if (!lists.get(i).isEmpty()) {
minHeap.offer(new int[]{lists.get(i).get(0), i, 0});
}
}
List<Integer> result = new ArrayList<>();
while (!minHeap.isEmpty()) {
int[] curr = minHeap.poll();
result.add(curr[0]);
int listIndex = curr[1];
int elementIndex = curr[2];
if (elementIndex + 1 < lists.get(listIndex).size()) {
minHeap.offer(new int[]{lists.get(listIndex).get(elementIndex + 1), listIndex, elementIndex + 1});
}
}Solution
Step 1: Analyze the comparator used in PriorityQueue
The comparator (b[0] - a[0]) reverses natural order, making it a max heap, not a min heap.Step 2: Effect of max heap on merging sorted lists
Using max heap causes the largest element to be polled first, breaking the merge logic which requires smallest element first.Final Answer:
Comparator reverses order, creating max heap instead of min heap -> Option CQuick Check:
Comparator direction = min heap needed [OK]
- Using (b[0] - a[0]) instead of (a[0] - b[0])
- Ignoring empty list checks
- Not incrementing elementIndex properly
Solution
Step 1: Compare time complexities of approaches
Inserting all elements at once (A) costs O(N log N) where N is total elements. Pairwise merging (C) costs more. Using max heap and reversing (D) is inefficient.Step 2: Explain min heap incremental insertion (B)
Inserting only first elements and adding next after extraction keeps heap size at K, leading to O(N log K) time, which is optimal.Final Answer:
Insert first elements only, add next after extraction -> Option AQuick Check:
Optimal merge = incremental min heap insertion [OK]
- Inserting all elements at once causing slow performance
- Using max heap incorrectly
- Ignoring heap size impact on complexity
Ready to prove it?
Pass the test and this pattern moves unknown
