Chapter 27: Heaps and Priority Queues
27.03Intermediate / Advanced4 topics17 min4 interactive animations

Heap algorithm design

Algorithms built on heaps: heap sort, the top-k elements, the k-th largest in a stream and merging k sorted lists.

Learning goals

  • Sort in place with a max-heap
  • Keep the k largest items of a stream with a min-heap of size k
  • Answer 'k-th largest so far' in O(log k) per item
  • Merge k sorted sequences in O(n log k)

Prerequisites

Heap operationsSorting algorithms

Heaps appear whenever a program repeatedly needs the best item of a changing collection. Four patterns cover most uses.

27.03.1

Heap sort

**Heap sort** builds a **max-heap** from the array in O(n), then repeatedly swaps the root (the maximum) with the last element of the heap, shrinks the heap by one and sifts the new root down. The sorted part grows at the end of the array, so the sort is **in place** and takes O(n log n) in every case, with O(1) extra memory. It is **not stable**, and its access pattern jumps around memory, so quick sort and merge sort are usually faster in practice.

In the “Unsorted” preset the animation sorts with 27 comparisons and 20 swaps; the “Duplicates” preset shows equal keys passing each other, which is why the sort is not stable.

Java
1static void heapSort(int[] a) {
2 int n = a.length;
3 for (int i = n / 2 - 1; i >= 0; i--) siftDownMax(a, n, i); // build a max-heap in O(n)
4 for (int end = n - 1; end > 0; end--) {
5 swap(a, 0, end); // the maximum goes to its final place
6 siftDownMax(a, end, 0); // restore the heap in the shorter prefix
7 }
8}
9static void siftDownMax(int[] a, int n, int i) {
10 while (true) {
11 int l = 2 * i + 1, r = l + 1, m = i;
12 if (l < n && a[l] > a[m]) m = l;
13 if (r < n && a[r] > a[m]) m = r;
14 if (m == i) return;
15 swap(a, i, m);
16 i = m;
17 }
18}

Best

O(n log n)

Average

O(n log n)

Worst

O(n log n)

Space

O(1)

Heap sort

Interactive · edit the input

Your input

Try

Heap as a binary tree · 8 nodes

4
0
10
1
3
2
5
3
1
4
8
5
7
6
2
7

4, 10, 3, 5, 1, 8, 7, 2

int[] arr · length 8

4
10
3
5
1
8
7
2
01234567
Being comparedJust swappedFinal position (outside the heap)
Setup

Phase 1 turns the array into a max-heap, where every parent is >= its children. Phase 2 repeatedly moves the maximum to the end.

Step 1 / 102
Java
1public static void heapSort(int[] arr) {
2 int n = arr.length;
3 for (int i = n / 2 - 1; i >= 0; i--) {
4 siftDown(arr, n, i); // build a max-heap
5 }
6 for (int end = n - 1; end > 0; end--) {
7 swap(arr, 0, end); // largest goes to its final slot
8 siftDown(arr, end, 0); // repair the smaller heap
9 }
10}
11
12static void siftDown(int[] arr, int size, int i) {
13 while (true) {
14 int largest = i, l = 2 * i + 1, r = 2 * i + 2;
15 if (l < size && arr[l] > arr[largest]) largest = l;
16 if (r < size && arr[r] > arr[largest]) largest = r;
17 if (largest == i) return;
18 swap(arr, i, largest);
19 i = largest;
20 }
21}

Live state

0 comparisons0 swaps

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 102 steps
StepLineStateWhat happens
1L2n=8Phase 1 turns the array into a max-heap, where every parent is >= its children. Phase 2 repeatedly moves the maximum to the end.
2L3i=3 n=8Heapify the subtree rooted at 3. Indices 4..7 are leaves and are already heaps.
3L14i=3 size=8 l=7 r=8Sift down from index 3 (5). Its children are at 7 and 8; 8 is outside the heap.
4L15i=3 l=7 largest=3The left child is not larger.
5L17i=3 largest=35 is at least as large as both children, so the heap property holds here.
6L3i=2 n=8Heapify the subtree rooted at 2. Indices 4..7 are leaves and are already heaps.
7L14i=2 size=8 l=5 r=6Sift down from index 2 (3). Its children are at 5 and 6.
8L15i=2 l=5 largest=2The left child is larger.
9L16i=2 r=6 largest=5The right child is not larger.
10L17i=2 largest=5A child is larger than 3, so swap with the larger child.
11L18i=2 largest=5Swapped arr[2] and arr[5]. Continue sifting from 5.
12L14i=5 size=8 l=11 r=12Sift down from index 5 (3). Its children are at 11 and 12, both outside the heap.
13L17i=5 largest=53 is at least as large as both children, so the heap property holds here.
14L3i=1 n=8Heapify the subtree rooted at 1. Indices 4..7 are leaves and are already heaps.
15L14i=1 size=8 l=3 r=4Sift down from index 1 (10). Its children are at 3 and 4.
16L15i=1 l=3 largest=1The left child is not larger.
17L16i=1 r=4 largest=1The right child is not larger.
18L17i=1 largest=110 is at least as large as both children, so the heap property holds here.
19L3i=0 n=8Heapify the subtree rooted at 0. Indices 4..7 are leaves and are already heaps.
20L14i=0 size=8 l=1 r=2Sift down from index 0 (4). Its children are at 1 and 2.
21L15i=0 l=1 largest=0The left child is larger.
22L16i=0 r=2 largest=1The right child is not larger.
23L17i=0 largest=1A child is larger than 4, so swap with the larger child.
24L18i=0 largest=1Swapped arr[0] and arr[1]. Continue sifting from 1.
25L14i=1 size=8 l=3 r=4Sift down from index 1 (4). Its children are at 3 and 4.
26L15i=1 l=3 largest=1The left child is larger.
27L16i=1 r=4 largest=3The right child is not larger.
28L17i=1 largest=3A child is larger than 4, so swap with the larger child.
29L18i=1 largest=3Swapped arr[1] and arr[3]. Continue sifting from 3.
30L14i=3 size=8 l=7 r=8Sift down from index 3 (4). Its children are at 7 and 8; 8 is outside the heap.
31L15i=3 l=7 largest=3The left child is not larger.
32L17i=3 largest=34 is at least as large as both children, so the heap property holds here.
33L5n=8Max-heap built: the largest value 10 is at the root.
34L7end=7Swap the root with arr[7]. 10 is now in its final position and the heap shrinks to 7 elements.
35L14i=0 size=7 l=1 r=2Sift down from index 0 (2). Its children are at 1 and 2.
36L15i=0 l=1 largest=0The left child is larger.
37L16i=0 r=2 largest=1The right child is larger still.
38L17i=0 largest=2A child is larger than 2, so swap with the larger child.
39L18i=0 largest=2Swapped arr[0] and arr[2]. Continue sifting from 2.
40L14i=2 size=7 l=5 r=6Sift down from index 2 (2). Its children are at 5 and 6.
41L15i=2 l=5 largest=2The left child is larger.
42L16i=2 r=6 largest=5The right child is larger still.
43L17i=2 largest=6A child is larger than 2, so swap with the larger child.
44L18i=2 largest=6Swapped arr[2] and arr[6]. Continue sifting from 6.
45L14i=6 size=7 l=13 r=14Sift down from index 6 (2). Its children are at 13 and 14, both outside the heap.
46L17i=6 largest=62 is at least as large as both children, so the heap property holds here.
47L7end=6Swap the root with arr[6]. 8 is now in its final position and the heap shrinks to 6 elements.
48L14i=0 size=6 l=1 r=2Sift down from index 0 (2). Its children are at 1 and 2.
49L15i=0 l=1 largest=0The left child is larger.
50L16i=0 r=2 largest=1The right child is larger still.
51L17i=0 largest=2A child is larger than 2, so swap with the larger child.
52L18i=0 largest=2Swapped arr[0] and arr[2]. Continue sifting from 2.
53L14i=2 size=6 l=5 r=6Sift down from index 2 (2). Its children are at 5 and 6; 6 is outside the heap.
54L15i=2 l=5 largest=2The left child is larger.
55L17i=2 largest=5A child is larger than 2, so swap with the larger child.
56L18i=2 largest=5Swapped arr[2] and arr[5]. Continue sifting from 5.
57L14i=5 size=6 l=11 r=12Sift down from index 5 (2). Its children are at 11 and 12, both outside the heap.
58L17i=5 largest=52 is at least as large as both children, so the heap property holds here.
59L7end=5Swap the root with arr[5]. 7 is now in its final position and the heap shrinks to 5 elements.
60L14i=0 size=5 l=1 r=2Sift down from index 0 (2). Its children are at 1 and 2.
61L15i=0 l=1 largest=0The left child is larger.
62L16i=0 r=2 largest=1The right child is not larger.
63L17i=0 largest=1A child is larger than 2, so swap with the larger child.
64L18i=0 largest=1Swapped arr[0] and arr[1]. Continue sifting from 1.
65L14i=1 size=5 l=3 r=4Sift down from index 1 (2). Its children are at 3 and 4.
66L15i=1 l=3 largest=1The left child is larger.
67L16i=1 r=4 largest=3The right child is not larger.
68L17i=1 largest=3A child is larger than 2, so swap with the larger child.
69L18i=1 largest=3Swapped arr[1] and arr[3]. Continue sifting from 3.
70L14i=3 size=5 l=7 r=8Sift down from index 3 (2). Its children are at 7 and 8, both outside the heap.
71L17i=3 largest=32 is at least as large as both children, so the heap property holds here.
72L7end=4Swap the root with arr[4]. 5 is now in its final position and the heap shrinks to 4 elements.
73L14i=0 size=4 l=1 r=2Sift down from index 0 (1). Its children are at 1 and 2.
74L15i=0 l=1 largest=0The left child is larger.
75L16i=0 r=2 largest=1The right child is not larger.
76L17i=0 largest=1A child is larger than 1, so swap with the larger child.
77L18i=0 largest=1Swapped arr[0] and arr[1]. Continue sifting from 1.
78L14i=1 size=4 l=3 r=4Sift down from index 1 (1). Its children are at 3 and 4; 4 is outside the heap.
79L15i=1 l=3 largest=1The left child is larger.
80L17i=1 largest=3A child is larger than 1, so swap with the larger child.
81L18i=1 largest=3Swapped arr[1] and arr[3]. Continue sifting from 3.
82L14i=3 size=4 l=7 r=8Sift down from index 3 (1). Its children are at 7 and 8, both outside the heap.
83L17i=3 largest=31 is at least as large as both children, so the heap property holds here.
84L7end=3Swap the root with arr[3]. 4 is now in its final position and the heap shrinks to 3 elements.
85L14i=0 size=3 l=1 r=2Sift down from index 0 (1). Its children are at 1 and 2.
86L15i=0 l=1 largest=0The left child is larger.
87L16i=0 r=2 largest=1The right child is larger still.
88L17i=0 largest=2A child is larger than 1, so swap with the larger child.
89L18i=0 largest=2Swapped arr[0] and arr[2]. Continue sifting from 2.
90L14i=2 size=3 l=5 r=6Sift down from index 2 (1). Its children are at 5 and 6, both outside the heap.
91L17i=2 largest=21 is at least as large as both children, so the heap property holds here.
92L7end=2Swap the root with arr[2]. 3 is now in its final position and the heap shrinks to 2 elements.
93L14i=0 size=2 l=1 r=2Sift down from index 0 (1). Its children are at 1 and 2; 2 is outside the heap.
94L15i=0 l=1 largest=0The left child is larger.
95L17i=0 largest=1A child is larger than 1, so swap with the larger child.
96L18i=0 largest=1Swapped arr[0] and arr[1]. Continue sifting from 1.
97L14i=1 size=2 l=3 r=4Sift down from index 1 (1). Its children are at 3 and 4, both outside the heap.
98L17i=1 largest=11 is at least as large as both children, so the heap property holds here.
99L7end=1Swap the root with arr[1]. 2 is now in its final position and the heap shrinks to 1 element.
100L14i=0 size=1 l=1 r=2Sift down from index 0 (1). Its children are at 1 and 2, both outside the heap.
101L17i=0 largest=01 is at least as large as both children, so the heap property holds here.
102L10Sorted in place with 27 comparisons and 20 swaps. Equal keys can swap past each other, so heap sort is not stable.
27.03.2

Top-K elements

To find the **k largest** of n items without sorting them all, keep a **min-heap of size k**. For each item: if the heap has fewer than k items, add it; otherwise, if the item is larger than the root (the smallest of the current k), replace the root with it. At the end the heap holds the k largest. The cost is O(n log k) time and O(k) space, far better than sorting when k is small, and it works on a stream that does not fit in memory.

The “K = 3” preset keeps 20, 17 and 14 with 15 comparisons. The input order matters for the cost: the “Increasing stream” preset replaces the root almost every time (17 comparisons) while the “Decreasing stream” preset never does after the first 3 items (7 comparisons).

Java
1static int[] topK(int[] a, int k) { // the k largest values, in descending order
2 PriorityQueue<Integer> heap = new PriorityQueue<>(); // min-heap: the root is the smallest of the k kept
3 for (int x : a) {
4 if (heap.size() < k) heap.add(x);
5 else if (x > heap.peek()) { heap.poll(); heap.add(x); }
6 }
7 int[] out = new int[heap.size()];
8 for (int i = out.length - 1; i >= 0; i--) out[i] = heap.poll();
9 return out;
10}

Best

O(n)

Average

O(n log k)

Worst

O(n log k)

Space

O(k)

Top K largest with a min-heap

Interactive · edit the input

Your input

Try

The heap is empty.

int[] arr · length 0

Being comparedJust movedSmallest of the K (root)

stream

50
121
32
173
94
205
16
147
Setup

Keep only the 3 largest values seen so far in a min-heap. Its root is the smallest of those K, so it is the one to beat.

Step 1 / 28
Java
1public static int[] topK(int[] nums, int k) {
2 PriorityQueue<Integer> heap = new PriorityQueue<>(); // min-heap
3 for (int x : nums) {
4 if (heap.size() < k) {
5 heap.add(x);
6 } else if (x > heap.peek()) {
7 heap.poll();
8 heap.add(x);
9 }
10 }
11 return heap.stream().mapToInt(i -> i).toArray();
12}

Live state

0 comparisons0 heap swaps

k

3

No condition evaluated on this step
Execution trace (dry run table)Show 28 steps
StepLineStateWhat happens
1L2k=3Keep only the 3 largest values seen so far in a min-heap. Its root is the smallest of those K, so it is the one to beat.
2L4x=5 heap.size=0 k=3The heap is not full yet, so add 5.
3L5x=5Add 5 at the end, then sift it up.
4L4x=12 heap.size=1 k=3The heap is not full yet, so add 12.
5L5x=12Add 12 at the end, then sift it up.
6L5child=12 parent=5Parent is not larger than child, so the heap property holds.
7L4x=3 heap.size=2 k=3The heap is not full yet, so add 3.
8L5x=3Add 3 at the end, then sift it up.
9L5child=3 parent=5A min-heap needs parent <= child, so swap them (sift up).
10L5moved=3Swapped; 3 moves up.
11L6x=17 heap.peek()=317 beats the smallest of the current top 3 (3), so replace it.
12L7x=17poll() removes the old root; add(17) lands at the root and sifts down.
13L7node=17 smallest=5A child is smaller than the node, so swap with the smaller child (sift down).
14L7Swapped; 5 moves up.
15L7node=17 smallest=17The new root is not larger than its children. Heap restored.
16L6x=9 heap.peek()=59 beats the smallest of the current top 3 (5), so replace it.
17L7x=9poll() removes the old root; add(9) lands at the root and sifts down.
18L7node=9 smallest=9The new root is not larger than its children. Heap restored.
19L6x=20 heap.peek()=920 beats the smallest of the current top 3 (9), so replace it.
20L7x=20poll() removes the old root; add(20) lands at the root and sifts down.
21L7node=20 smallest=12A child is smaller than the node, so swap with the smaller child (sift down).
22L7Swapped; 12 moves up.
23L7node=20 smallest=20The new root is not larger than its children. Heap restored.
24L6x=1 heap.peek()=121 does not beat 12, so it cannot be in the top 3. Skip it.
25L6x=14 heap.peek()=1214 beats the smallest of the current top 3 (12), so replace it.
26L7x=14poll() removes the old root; add(14) lands at the root and sifts down.
27L7node=14 smallest=14The new root is not larger than its children. Heap restored.
28L11return=[20, 17, 14]The heap holds the 3 largest values: 20, 17, 14. Cost O(n log k) time and O(k) space, with 15 comparisons here.
27.03.3

Kth largest stream

'What is the k-th largest value so far?' over a stream is the same structure: a min-heap that holds the k largest values seen, whose **root is the answer**. Each new value costs O(log k), and the query is O(1). It never needs the values that fell out of the top k.

Until k values have arrived there is no k-th largest; define what to return (here, null) rather than letting the code fail on an empty heap.

Java
1static class KthLargest {
2 private final int k;
3 private final PriorityQueue<Integer> heap = new PriorityQueue<>(); // the k largest values seen
4 KthLargest(int k) { this.k = k; }
5 Integer add(int x) { // returns the k-th largest so far, or null if fewer than k values
6 if (heap.size() < k) heap.add(x);
7 else if (x > heap.peek()) { heap.poll(); heap.add(x); }
8 return heap.size() < k ? null : heap.peek();
9 }
10}

Best

O(log k)

Average

O(log k)

Worst

O(log k)

Space

O(k)

Top K largest with a min-heap

Interactive · edit the input

Your input

Try

The heap is empty.

int[] arr · length 0

Being comparedJust movedSmallest of the K (root)

stream

50
121
32
173
94
205
16
147
Setup

Keep only the 3 largest values seen so far in a min-heap. Its root is the smallest of those K, so it is the one to beat.

Step 1 / 28
Java
1public static int[] topK(int[] nums, int k) {
2 PriorityQueue<Integer> heap = new PriorityQueue<>(); // min-heap
3 for (int x : nums) {
4 if (heap.size() < k) {
5 heap.add(x);
6 } else if (x > heap.peek()) {
7 heap.poll();
8 heap.add(x);
9 }
10 }
11 return heap.stream().mapToInt(i -> i).toArray();
12}

Live state

0 comparisons0 heap swaps

k

3

No condition evaluated on this step
Execution trace (dry run table)Show 28 steps
StepLineStateWhat happens
1L2k=3Keep only the 3 largest values seen so far in a min-heap. Its root is the smallest of those K, so it is the one to beat.
2L4x=5 heap.size=0 k=3The heap is not full yet, so add 5.
3L5x=5Add 5 at the end, then sift it up.
4L4x=12 heap.size=1 k=3The heap is not full yet, so add 12.
5L5x=12Add 12 at the end, then sift it up.
6L5child=12 parent=5Parent is not larger than child, so the heap property holds.
7L4x=3 heap.size=2 k=3The heap is not full yet, so add 3.
8L5x=3Add 3 at the end, then sift it up.
9L5child=3 parent=5A min-heap needs parent <= child, so swap them (sift up).
10L5moved=3Swapped; 3 moves up.
11L6x=17 heap.peek()=317 beats the smallest of the current top 3 (3), so replace it.
12L7x=17poll() removes the old root; add(17) lands at the root and sifts down.
13L7node=17 smallest=5A child is smaller than the node, so swap with the smaller child (sift down).
14L7Swapped; 5 moves up.
15L7node=17 smallest=17The new root is not larger than its children. Heap restored.
16L6x=9 heap.peek()=59 beats the smallest of the current top 3 (5), so replace it.
17L7x=9poll() removes the old root; add(9) lands at the root and sifts down.
18L7node=9 smallest=9The new root is not larger than its children. Heap restored.
19L6x=20 heap.peek()=920 beats the smallest of the current top 3 (9), so replace it.
20L7x=20poll() removes the old root; add(20) lands at the root and sifts down.
21L7node=20 smallest=12A child is smaller than the node, so swap with the smaller child (sift down).
22L7Swapped; 12 moves up.
23L7node=20 smallest=20The new root is not larger than its children. Heap restored.
24L6x=1 heap.peek()=121 does not beat 12, so it cannot be in the top 3. Skip it.
25L6x=14 heap.peek()=1214 beats the smallest of the current top 3 (12), so replace it.
26L7x=14poll() removes the old root; add(14) lands at the root and sifts down.
27L7node=14 smallest=14The new root is not larger than its children. Heap restored.
28L11return=[20, 17, 14]The heap holds the 3 largest values: 20, 17, 14. Cost O(n log k) time and O(k) space, with 15 comparisons here.
27.03.4

Merge K sorted lists/arrays

To merge k sorted sequences, keep a min-heap of their **current heads**, one entry per sequence: repeatedly remove the smallest, output it, and push the next element of the sequence it came from. The heap never holds more than k entries, so each of the n outputs costs O(log k), for O(n log k) in all, compared with O(n · k) for scanning all k heads each time.

In the animation the “3 runs” preset merges 9 values with 15 comparisons of run heads and the “2 runs” preset needs only 8, because it scans the heads directly; a heap replaces that O(k) scan by an O(log k) step, which matters for large k. This is also the merge step of external sorting, where each run is a sorted file too large for memory.

Java
1static List<Integer> mergeK(int[][] lists) {
2 PriorityQueue<int[]> heap = new PriorityQueue<>(Comparator.comparingInt(e -> e[0])); // {value, list, index}
3 for (int i = 0; i < lists.length; i++) if (lists[i].length > 0) heap.add(new int[]{lists[i][0], i, 0});
4 List<Integer> out = new ArrayList<>();
5 while (!heap.isEmpty()) {
6 int[] top = heap.poll(); // the smallest head
7 out.add(top[0]);
8 int next = top[2] + 1;
9 if (next < lists[top[1]].length) heap.add(new int[]{lists[top[1]][next], top[1], next});
10 }
11 return out;
12}

Best

O(n log k)

Average

O(n log k)

Worst

O(n log k)

Space

O(k)

K-way merge of sorted runs

Interactive · edit the input

Your input

Try

run 0

7pos[0]
19
34

run 1

2pos[1]
11
28

run 2

5pos[2]
23
40

out[]

empty
Setup

3 sorted runs to merge. In an external sort these would be chunks that each fit in memory and were sorted separately, then written to disk.

Step 1 / 38
Java
1public static int[] kWayMerge(int[][] runs) {
2 int k = runs.length, total = 0;
3 for (int[] r : runs) total += r.length;
4 int[] pos = new int[k]; // next unread index in each run
5 int[] out = new int[total];
6 for (int o = 0; o < total; o++) {
7 int best = -1;
8 for (int r = 0; r < k; r++) {
9 if (pos[r] < runs[r].length &&
10 (best == -1 || runs[r][pos[r]] < runs[best][pos[best]])) {
11 best = r;
12 }
13 }
14 out[o] = runs[best][pos[best]++];
15 }
16 return out;
17}

Live state

0 comparisons

o

0

pos[0]

0

pos[1]

0

pos[2]

0

No condition evaluated on this step
Execution trace (dry run table)Show 38 steps
StepLineStateWhat happens
1L4o=0 pos[0]=0 pos[1]=0 pos[2]=03 sorted runs to merge. In an external sort these would be chunks that each fit in memory and were sorted separately, then written to disk.
2L11o=0 pos[0]=0 pos[1]=0 pos[2]=0 best=0 r=0Run 0's head 7 is the first candidate.
3L10o=0 pos[0]=0 pos[1]=0 pos[2]=0 best=0 r=1Run 1's head is smaller, so it becomes the new best.
4L10o=0 pos[0]=0 pos[1]=0 pos[2]=0 best=1 r=2Run 1's head is still the smallest.
5L14o=1 pos[0]=0 pos[1]=1 pos[2]=0 best=1out[0] = 2, taken from run 1.
6L11o=1 pos[0]=0 pos[1]=1 pos[2]=0 best=0 r=0Run 0's head 7 is the first candidate.
7L10o=1 pos[0]=0 pos[1]=1 pos[2]=0 best=0 r=1Run 0's head is still the smallest.
8L10o=1 pos[0]=0 pos[1]=1 pos[2]=0 best=0 r=2Run 2's head is smaller, so it becomes the new best.
9L14o=2 pos[0]=0 pos[1]=1 pos[2]=1 best=2out[1] = 5, taken from run 2.
10L11o=2 pos[0]=0 pos[1]=1 pos[2]=1 best=0 r=0Run 0's head 7 is the first candidate.
11L10o=2 pos[0]=0 pos[1]=1 pos[2]=1 best=0 r=1Run 0's head is still the smallest.
12L10o=2 pos[0]=0 pos[1]=1 pos[2]=1 best=0 r=2Run 0's head is still the smallest.
13L14o=3 pos[0]=1 pos[1]=1 pos[2]=1 best=0out[2] = 7, taken from run 0.
14L11o=3 pos[0]=1 pos[1]=1 pos[2]=1 best=0 r=0Run 0's head 19 is the first candidate.
15L10o=3 pos[0]=1 pos[1]=1 pos[2]=1 best=0 r=1Run 1's head is smaller, so it becomes the new best.
16L10o=3 pos[0]=1 pos[1]=1 pos[2]=1 best=1 r=2Run 1's head is still the smallest.
17L14o=4 pos[0]=1 pos[1]=2 pos[2]=1 best=1out[3] = 11, taken from run 1.
18L11o=4 pos[0]=1 pos[1]=2 pos[2]=1 best=0 r=0Run 0's head 19 is the first candidate.
19L10o=4 pos[0]=1 pos[1]=2 pos[2]=1 best=0 r=1Run 0's head is still the smallest.
20L10o=4 pos[0]=1 pos[1]=2 pos[2]=1 best=0 r=2Run 0's head is still the smallest.
21L14o=5 pos[0]=2 pos[1]=2 pos[2]=1 best=0out[4] = 19, taken from run 0.
22L11o=5 pos[0]=2 pos[1]=2 pos[2]=1 best=0 r=0Run 0's head 34 is the first candidate.
23L10o=5 pos[0]=2 pos[1]=2 pos[2]=1 best=0 r=1Run 1's head is smaller, so it becomes the new best.
24L10o=5 pos[0]=2 pos[1]=2 pos[2]=1 best=1 r=2Run 2's head is smaller, so it becomes the new best.
25L14o=6 pos[0]=2 pos[1]=2 pos[2]=2 best=2out[5] = 23, taken from run 2.
26L11o=6 pos[0]=2 pos[1]=2 pos[2]=2 best=0 r=0Run 0's head 34 is the first candidate.
27L10o=6 pos[0]=2 pos[1]=2 pos[2]=2 best=0 r=1Run 1's head is smaller, so it becomes the new best.
28L10o=6 pos[0]=2 pos[1]=2 pos[2]=2 best=1 r=2Run 1's head is still the smallest.
29L14o=7 pos[0]=2 pos[1]=3 pos[2]=2 best=1out[6] = 28, taken from run 1.
30L11o=7 pos[0]=2 pos[1]=3 pos[2]=2 best=0 r=0Run 0's head 34 is the first candidate.
31L9o=7 pos[0]=2 pos[1]=3 pos[2]=2 best=0 r=1Run 1 is exhausted, skip it.
32L10o=7 pos[0]=2 pos[1]=3 pos[2]=2 best=0 r=2Run 0's head is still the smallest.
33L14o=8 pos[0]=3 pos[1]=3 pos[2]=2 best=0out[7] = 34, taken from run 0.
34L9o=8 pos[0]=3 pos[1]=3 pos[2]=2 r=0Run 0 is exhausted, skip it.
35L9o=8 pos[0]=3 pos[1]=3 pos[2]=2 r=1Run 1 is exhausted, skip it.
36L11o=8 pos[0]=3 pos[1]=3 pos[2]=2 best=2 r=2Run 2's head 40 is the first candidate.
37L14o=9 pos[0]=3 pos[1]=3 pos[2]=3 best=2out[8] = 40, taken from run 2.
38L16return=[2, 5, 7, 11, 19, 23, 28, 34, 40]Merged 9 values with 15 comparisons. Scanning k heads costs O(k) per value, so a min-heap of the heads makes it O(log k) and the total O(n log k).

Practice ladder

Now try it yourself

Easy

  • Kth Largest Element in a Stream
  • Last Stone Weight

Medium

  • Top K Frequent Elements
  • K Closest Points to Origin
  • Merge k Sorted Lists

Advanced

  • Smallest Range Covering Elements from K Lists
  • Find K Pairs with Smallest Sums

Key takeaways

  • Heap sort: build a max-heap, then repeatedly swap the root to the end; O(n log n), in place, not stable.
  • Top-k with a min-heap of size k costs O(n log k); the root is the smallest kept value.
  • The k-th largest of a stream is the root of that heap, in O(log k) per item.
  • Merging k sorted lists with a heap of their heads costs O(n log k).