โข k == lists.lengthโข 0 โค k โค 10โดโข 0 โค lists[i].length โค 500โข -10โด โค lists[i][j] โค 10โดโข Total nodes โค 10โด
02
Section Two ยท Approach 1
Merge One-by-One โ O(n ยท k)
Merge list 1 into list 2, then result into list 3, etc. Each merge is O(n), and we do k-1 merges. The merged list grows, making later merges expensive: total O(nยทk).
Problem: The merged list keeps growing, so each subsequent merge gets longer. Using a min-heap or divide-and-conquer ensures we always pick the smallest element efficiently, reducing to O(n log k).
03
Section Three ยท Approach 2
Min-Heap / Divide & Conquer โ O(n log k)
Min-Heap: Maintain a heap of size k holding the current head of each list. Pop the smallest, link it to the result, push the next node from that list. Each pop/push is O(log k), and we process n total nodes.
Divide & Conquer: Pair up lists and merge each pair. Repeat until one list remains โ like merge sort's merge phase. log k rounds, each processing n total nodes.
๐ก Mental model (Heap): Imagine k conveyor belts, each delivering items in order. A judge stands at the merge point with a "smallest item display" (the heap). They always pick from the belt with the smallest current item and place it on the output belt. Checking k belts with a heap takes O(log k) per pick.
Algorithm โ Min-Heap
Step 1: Add the head of each non-null list to a min-heap (ordered by node value).
Step 2: While heap is not empty: pop smallest node, link to result.
Step 3: If popped node has a next, push next to heap.
Step 4: Return dummy.next.
๐ฏ Divide & Conquer alternative:
Merge lists[0] with lists[k/2], lists[1] with lists[k/2+1], etc. Reduces k lists to k/2. Repeat log k times.
Same O(n log k) complexity, no heap needed โ useful in languages without a built-in priority queue.
04
Section Four ยท Trace
Visual Walkthrough
Trace min-heap approach with [[1,4,5],[1,3,4],[2,6]]:
Min-Heap โ always extract the smallest head
05
Section Five ยท Implementation
Code โ Java & Python
Java โ Min-Heap (PriorityQueue)
import java.util.PriorityQueue;
classSolution {
publicListNodemergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> heap = newPriorityQueue<>(
(a, b) -> a.val - b.val);
for (ListNode head : lists)
if (head != null) heap.offer(head);
ListNode dummy = newListNode(0);
ListNode tail = dummy;
while (!heap.isEmpty()) {
ListNode node = heap.poll();
tail.next = node;
tail = tail.next;
if (node.next != null)
heap.offer(node.next);
}
return dummy.next;
}
}
Python โ Min-Heap (heapq)
import heapq
classSolution:
defmergeKLists(self, lists: list) -> Optional[ListNode]:
heap = []
for i, head in enumerate(lists):
if head:
heapq.heappush(heap, (head.val, i, head))
dummy = tail = ListNode(0)
while heap:
val, i, node = heapq.heappop(heap)
tail.next = node
tail = tail.next
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.next
06
Section Six ยท Analysis
Complexity Analysis
Approach
Time
Space
Trade-off
Merge one-by-one
O(n ยท k)
O(1)
Simple but slow for large k
Collect + sort
O(n log n)
O(n)
Ignores sorted structure
Min-Heap โ optimal
O(n log k)
O(k)
Heap of size k, pop/push log k each
Divide & Conquer
O(n log k)
O(log k)
Recursion stack for pairing
n = total nodes across all lists.:
The heap holds at most k nodes (one per list).
Each of the n nodes is pushed and popped once โ O(log k) per operation โ O(n log k) total.
07
Section Seven ยท Edge Cases
Edge Cases & Pitfalls
Case
Input
Why It Matters
Empty array
[]
No lists โ return null. Heap starts empty.
All empty lists
[null, null]
Nothing added to heap โ return null.
Single list
[[1,2,3]]
Return it as-is โ no merge needed.
k=10โด, short lists
Many single-node lists
Heap of 10โด โ still fast (log 10โด โ 14).
โ Common Mistake (Python): Pushing (node.val, node) to heapq. When two nodes have the same value, Python tries to compare nodes (which aren't comparable). Include a tiebreaker index: (node.val, i, node) where i is a unique counter.