Skip to content

Coding patternsBlind 75

Merge k Sorted Lists

The next node is always the smallest of the k heads, and a min-heap finds it in log k steps. Solved two ways, scanning every head in O(k·N) and a min-heap in O(N log k), with a step-through, code in five languages and the Big O worked out from the code.

You are given an array lists of k linked lists (k is the length of lists). Each list is sorted from small to large. Merge them all into one sorted linked list and return its head.

Example 1

Input: lists = [[2, 6, 9], [1, 7], [3, 4, 8]]

Output: [1, 2, 3, 4, 6, 7, 8, 9]

All 8 nodes from the 3 lists, in one sorted chain.

list 0
269
head
list 1
17
head
list 2
348
head
merged
12346789

Example 2

Input: lists = []

Output: []

No lists at all, so the result is empty.

Example 3

Input: lists = [[], [5]]

Output: [5]

Empty lists add nothing.

  • 0 ≤ k ≤ 10,000
  • 0 ≤ length of each list ≤ 500
  • −10,000 ≤ Node.val ≤ 10,000
  • Each list is sorted from small to large.
  • N, the total number of nodes, is at most 10,000.
Can k be 0, or a list be empty?
Yes, both. Empty lists simply add nothing.
Should we reuse the nodes?
Yes. Relink the existing nodes. No copies are needed.
Which matters more, k or N?
Both can be 10,000. A good answer must not multiply them together.
Can values repeat?
Yes. Equal values may come out in any order.

Like merging two lists, the next node is always one of the current heads, the smallest one. With k heads, finding that smallest one is the real cost. A structure that keeps “the smallest of a changing set” ready is a min-heap: a tree kept so the smallest item is always on top. Pop and push both take about log k steps.

Two other ways work too. Copying every value and sorting costs O(N log N) and copies all the nodes. Merging the lists in pairs, round after round, is also O(N log k), the same as the heap.

Min-heap O(N log k)

Put the head of every list into a min-heap, ordered by value. Then repeat:

  1. Pop the smallest node. Link it after the tail.
  2. If that node has a next node, push the next node in. It is the new head of that list.

The heap never holds more than one node per list, so at most k nodes. Finding the smallest is free (it is on top), and a pop or a push costs about log₂ k steps.

Why it is correct: the smallest node left is always the head of some list, and every list’s head is in the heap. So the popped node is always the smallest one left.

list 0
269
head
list 1
17
head
list 2
348
head
merged
min-heap smallest first
1 (list 1)2 (list 0)3 (list 2)
  1. heap = [(node.val, i, node) for i, node in enumerate(lists) if node]
  2. heapify(heap)
  3. dummy = tail = ListNode()
  4. while heap:
  5. val, i, node = heappop(heap)
  6. tail.next = node; tail = node
  7. if node.next:
  8. heappush(heap, (node.next.val, i, node.next))
  9. return dummy.next

Put the head of each list in a min-heap. The heap always holds at most one node per list, so at most 3 nodes.

1 / 10
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Min-heap: keep one head per list in a heap, so the smallest is always on
// top. N pops and pushes of log k each: O(N log k) time, O(k) memory.
public ListNode mergeKLists(ListNode[] lists) {
java.util.PriorityQueue<ListNode> heap =
new java.util.PriorityQueue<>((first, second) -> Integer.compare(first.val, second.val));
for (ListNode node : lists) {
if (node != null) heap.add(node);
}
ListNode dummy = new ListNode(), tail = dummy;
while (!heap.isEmpty()) {
ListNode node = heap.poll();
tail.next = node;
tail = node;
// The node after it is the new head of its list, so it joins the heap.
if (node.next != null) heap.add(node.next);
}
return dummy.next;
}
}
Time O(N log k)Space O(k)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    Each run of the loop does one pop and at most one push. On a heap with at most k nodes, each of them costs about log₂ k steps. There is one loop.

    1. heap = [(node.val, i, node) for i, node in enumerate(lists) if node]
    2. heapify(heap)
    3. dummy = tail = ListNode()
    4. while heap:
    5. val, i, node = heappop(heap)
    6. tail.next = node; tail = node
    7. if node.next:
    8. heappush(heap, (node.next.val, i, node.next))
    9. return dummy.next
  2. 2 Count the runs on Example 1

    Example 1 has N = 8 nodes, so 8 pops. The heap holds at most 3 nodes. The first 5 pops each push the next node of the same list. The last 3 find their list empty, and the heap shrinks.

    8 pushes (3 at the start, 5 later) + 8 pops = 16 heap operations, each on at most 3 nodes.

    pop 1pop 2pop 3pop 4pop 5pop 6pop 7pop 8
    heap size33333321
    push?yesyesyesyesyesnonono
  3. 3 Write the count for any n

    Every node goes into the heap once and comes out once. Each of those costs about log₂ k steps:

    about 2N × log₂ k

    Check it with Example 1: 2 × 8 = 16 heap operations, each on a heap of at most 3 nodes.

  4. 4 Keep only the part that grows fastest

    Drop the 2. What is left is N log k. Compared with k·N, the k became log k. For k = 10,000, that is about 14 instead of 10,000.

    So the time is O(N log k).

  5. S Space: count the extra memory the same way

    The heap holds at most one node per list, so k entries. The space is O(k).

    heap (up to k nodes)dummytail

2,000 random inputs of 0 to 5 lists with 0 to 4 nodes each, with empty lists and many equal values. Scan all heads and the min-heap gave the same merged list every time.

Largest inputsResult
10,000 lists of one node each (the most lists allowed). Min-heap.0.006 s, and about 0.8 MB of extra memory: the heap holds one entry per list, which is the O(k) space.
20 lists of 500 nodes, 10,000 nodes in total (the longest lists allowed). Min-heap.0.002 s, and under 0.001 MB of extra memory: the heap holds only 20 entries.
1,000 lists of one node each. Scan all heads.0.04 s. At 10,000 lists it would scan 10,000 heads for each of 10,000 nodes, 100 million steps, which is why the heap matters.