LeetCode #23 Hard

Merge K Sorted Lists

Merge k sorted linked lists into one sorted list.

heaplinked-listdivide-and-conquer
Open on LeetCode ↗
02

Intuition

At any moment the next output node is the smallest of the k current heads. A min-heap of heads answers that in O(log k): pop the winner, append it, push its successor. Total n nodes × log k.

How to spot this pattern

Merging two sorted lists is a pointer comparison; merging k of them is the same idea with a heap deciding which pointer to advance. The general pattern: when you repeatedly need the minimum across k moving frontiers, a heap of size k gives it in O(log k) instead of an O(k) scan. Only the k current heads ever need to be in memory — not the whole input.

03

Approach

1

Heap of current heads

Seed with each list's head. Ties on value need a tiebreaker (list index) since ListNodes don't compare.

2

Pop, append, replenish

The popped node joins the output tail; its next (if any) enters the heap. The heap never exceeds k.

3

Alternative: pairwise merge

Merging lists two at a time in rounds is also O(n log k) — the heap version streams and is simpler to reason about.

04

Solution & live demo

1import heapq
2 
3class Solution:
4 def mergeKLists(self, lists):
5 heap = [(node.val, i, node) for i, node in enumerate(lists) if node]
6 heapq.heapify(heap)
7 dummy = tail = ListNode(0)
8 while heap:
9 val, i, node = heapq.heappop(heap)
10 tail.next = node
11 tail = node
12 if node.next:
13 heapq.heappush(heap, (node.next.val, i, node.next))
14 return dummy.next
05

Common pitfalls

Putting nodes in the heap without a tiebreaker

✗ Wrong
heap = [(node.val, node) for node in lists if node]
✓ Right
heap = [(node.val, i, node) for i, node in enumerate(lists) if node]

When two nodes share a value, Python falls through to comparing the second tuple element — and ListNode defines no <, so it raises TypeError. The list index is unique, so it settles ties before any node comparison happens.

Pushing every node upfront

✗ Wrong
heap = [(n.val, i, n) for i, l in enumerate(lists)
        for n in iterate(l)]
✓ Right
heap = [(node.val, i, node) for i, node in enumerate(lists) if node]
# push each successor only as its predecessor is popped

That's O(N) space for N total nodes and turns each operation into O(log N). Holding only the k list heads keeps the heap at size k, so space is O(k) and each push is O(log k).

Returning dummy instead of dummy.next

✗ Wrong
return dummy
✓ Right
return dummy.next

dummy is a placeholder node holding value 0 that exists only so tail has something to attach to on the first iteration. It is not part of the answer — the real head is whatever got linked after it.

06

Edge cases

Some lists empty / all empty

Empty lists never enter the heap; all-empty returns None.

Equal values across lists

The (val, idx) tuple breaks ties deterministically.

07

Complexity

Time
O(n log k)
Space
O(k)
n total nodes; heap holds one node per list.