Merge K Sorted Lists: The Heap of Heads
This one is a Hard-rated classic reported from Amazon, Meta, Google interviews: "Merge k Sorted Lists". Like every post in this series, the goal is not memorizing the answer — it is recognizing the Top-K and Heaps pattern on sight. Part 10 of 11 in the Top-K and Heaps arc.
The Problem
Merge k sorted linked lists into one sorted list. A top-five question by reported frequency for over a decade — and the canonical k-way merge, the primitive under external sorting, log-structured storage compaction, and stream joins, which is why it refuses to die.
Input: lists = [[1, 4, 5], [1, 3, 4], [2, 6]]
Output: [1, 1, 2, 3, 4, 4, 5, 6]
Recognizing the Top-K and Heaps Pattern
When a problem asks for the k largest, smallest, closest, or most frequent — or for repeated access to an extreme while data changes — a heap gives O(log n) insertion and O(1) access to the extreme. The signature trick: keep a bounded heap of size k with the opposite polarity (a min-heap to track the k largest), evicting the root on overflow. Python's heapq is a min-heap; negate values for max behavior.
The next output element is always the minimum among k current heads — repeated min over a changing k-sized set, the heap's defining job. Pairwise sequential merging costs O(n·k); the heap (or divide-and-conquer pairing) brings it to O(n log k), and that gap is the whole interview.
The Approach
Push each non-empty head as (val, idx, node) — the list index breaks ties, because heapq falls back to comparing tuple fields and ListNode does not support comparison. Pop the min, splice it onto a dummy-headed tail, push the popped node's successor.
Divide-and-conquer (merge lists pairwise, halving rounds) achieves the same O(n log k) with O(1) extra space and is the better answer if the interviewer bans the heap — carry both.
Python Solution
import heapq
class ListNode:
def __init__(self, val: int = 0, next: "ListNode | None" = None):
self.val = val
self.next = next
def merge_k_lists(lists: list[ListNode | None]) -> ListNode | None:
"""Merge k sorted linked lists via a heap of current heads."""
heap: list[tuple[int, int, ListNode]] = []
for i, node in enumerate(lists):
if node:
heapq.heappush(heap, (node.val, i, node))
dummy = ListNode()
tail = dummy
while heap:
_, i, node = heapq.heappop(heap)
tail.next = node
tail = node
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.next
Complexity
- Time: O(n log k) — each of n nodes passes through a k-sized heap once
- Space: O(k) — the heap of heads (output reuses existing nodes)
Interview Tips and Follow-Ups
- The (val, idx, node) tie-break tuple is the Python-specific trap — equal vals fall through to comparing nodes and crash without the index. Know it cold.
- Sketch the divide-and-conquer alternative and its recurrence (n per round, log k rounds) — asked for at Google routinely.
- This is external merge sort's inner loop — one sentence connecting it to sorting 100GB with 1GB of RAM upgrades the conversation to systems.
That wraps part 10 of the Top-K and Heaps arc. The full Technical Interview category maps all one hundred questions to the nine patterns that dominate FAANG screens — work through an arc end to end and the next unseen variant will feel familiar.
Keep reading
Find Median From a Data Stream With Two Heaps
The two-heap balance: max-heap low half, min-heap high half. Python solution and complexity analysis for the heap and top-k interview pattern.
K Closest Points to Origin Without Square Roots
Bounded max-heap on squared distance — monotone transforms are free. Python solution and complexity analysis for the heap and top-k interview pattern.
Find K Pairs With Smallest Sums: Frontier Expansion
Explore an implicit sorted matrix — seed one row, expand neighbors lazily. Python solution and complexity analysis for the heap and top-k interview pattern.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
- No comments yet. Be the first.