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
Heaps appear whenever a program repeatedly needs the best item of a changing collection. Four patterns cover most uses.
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.
1static void heapSort(int[] a) {2int n = a.length;3for (int i = n / 2 - 1; i >= 0; i--) siftDownMax(a, n, i); // build a max-heap in O(n)4for (int end = n - 1; end > 0; end--) {5swap(a, 0, end); // the maximum goes to its final place6siftDownMax(a, end, 0); // restore the heap in the shorter prefix7}8}9static void siftDownMax(int[] a, int n, int i) {10while (true) {11int l = 2 * i + 1, r = l + 1, m = i;12if (l < n && a[l] > a[m]) m = l;13if (r < n && a[r] > a[m]) m = r;14if (m == i) return;15swap(a, i, m);16i = 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 inputHeap as a binary tree · 8 nodes
4, 10, 3, 5, 1, 8, 7, 2
int[] arr · length 8
Phase 1 turns the array into a max-heap, where every parent is >= its children. Phase 2 repeatedly moves the maximum to the end.
1public static void heapSort(int[] arr) {2int n = arr.length;3for (int i = n / 2 - 1; i >= 0; i--) {4siftDown(arr, n, i); // build a max-heap5}6for (int end = n - 1; end > 0; end--) {7swap(arr, 0, end); // largest goes to its final slot8siftDown(arr, end, 0); // repair the smaller heap9}10}1112static void siftDown(int[] arr, int size, int i) {13while (true) {14int largest = i, l = 2 * i + 1, r = 2 * i + 2;15if (l < size && arr[l] > arr[largest]) largest = l;16if (r < size && arr[r] > arr[largest]) largest = r;17if (largest == i) return;18swap(arr, i, largest);19i = largest;20}21}
Live state
n
8
Execution trace (dry run table)Show 102 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=8 | Phase 1 turns the array into a max-heap, where every parent is >= its children. Phase 2 repeatedly moves the maximum to the end. |
| 2 | L3 | i=3 n=8 | Heapify the subtree rooted at 3. Indices 4..7 are leaves and are already heaps. |
| 3 | L14 | i=3 size=8 l=7 r=8 | Sift down from index 3 (5). Its children are at 7 and 8; 8 is outside the heap. |
| 4 | L15 | i=3 l=7 largest=3 | The left child is not larger. |
| 5 | L17 | i=3 largest=3 | 5 is at least as large as both children, so the heap property holds here. |
| 6 | L3 | i=2 n=8 | Heapify the subtree rooted at 2. Indices 4..7 are leaves and are already heaps. |
| 7 | L14 | i=2 size=8 l=5 r=6 | Sift down from index 2 (3). Its children are at 5 and 6. |
| 8 | L15 | i=2 l=5 largest=2 | The left child is larger. |
| 9 | L16 | i=2 r=6 largest=5 | The right child is not larger. |
| 10 | L17 | i=2 largest=5 | A child is larger than 3, so swap with the larger child. |
| 11 | L18 | i=2 largest=5 | Swapped arr[2] and arr[5]. Continue sifting from 5. |
| 12 | L14 | i=5 size=8 l=11 r=12 | Sift down from index 5 (3). Its children are at 11 and 12, both outside the heap. |
| 13 | L17 | i=5 largest=5 | 3 is at least as large as both children, so the heap property holds here. |
| 14 | L3 | i=1 n=8 | Heapify the subtree rooted at 1. Indices 4..7 are leaves and are already heaps. |
| 15 | L14 | i=1 size=8 l=3 r=4 | Sift down from index 1 (10). Its children are at 3 and 4. |
| 16 | L15 | i=1 l=3 largest=1 | The left child is not larger. |
| 17 | L16 | i=1 r=4 largest=1 | The right child is not larger. |
| 18 | L17 | i=1 largest=1 | 10 is at least as large as both children, so the heap property holds here. |
| 19 | L3 | i=0 n=8 | Heapify the subtree rooted at 0. Indices 4..7 are leaves and are already heaps. |
| 20 | L14 | i=0 size=8 l=1 r=2 | Sift down from index 0 (4). Its children are at 1 and 2. |
| 21 | L15 | i=0 l=1 largest=0 | The left child is larger. |
| 22 | L16 | i=0 r=2 largest=1 | The right child is not larger. |
| 23 | L17 | i=0 largest=1 | A child is larger than 4, so swap with the larger child. |
| 24 | L18 | i=0 largest=1 | Swapped arr[0] and arr[1]. Continue sifting from 1. |
| 25 | L14 | i=1 size=8 l=3 r=4 | Sift down from index 1 (4). Its children are at 3 and 4. |
| 26 | L15 | i=1 l=3 largest=1 | The left child is larger. |
| 27 | L16 | i=1 r=4 largest=3 | The right child is not larger. |
| 28 | L17 | i=1 largest=3 | A child is larger than 4, so swap with the larger child. |
| 29 | L18 | i=1 largest=3 | Swapped arr[1] and arr[3]. Continue sifting from 3. |
| 30 | L14 | i=3 size=8 l=7 r=8 | Sift down from index 3 (4). Its children are at 7 and 8; 8 is outside the heap. |
| 31 | L15 | i=3 l=7 largest=3 | The left child is not larger. |
| 32 | L17 | i=3 largest=3 | 4 is at least as large as both children, so the heap property holds here. |
| 33 | L5 | n=8 | Max-heap built: the largest value 10 is at the root. |
| 34 | L7 | end=7 | Swap the root with arr[7]. 10 is now in its final position and the heap shrinks to 7 elements. |
| 35 | L14 | i=0 size=7 l=1 r=2 | Sift down from index 0 (2). Its children are at 1 and 2. |
| 36 | L15 | i=0 l=1 largest=0 | The left child is larger. |
| 37 | L16 | i=0 r=2 largest=1 | The right child is larger still. |
| 38 | L17 | i=0 largest=2 | A child is larger than 2, so swap with the larger child. |
| 39 | L18 | i=0 largest=2 | Swapped arr[0] and arr[2]. Continue sifting from 2. |
| 40 | L14 | i=2 size=7 l=5 r=6 | Sift down from index 2 (2). Its children are at 5 and 6. |
| 41 | L15 | i=2 l=5 largest=2 | The left child is larger. |
| 42 | L16 | i=2 r=6 largest=5 | The right child is larger still. |
| 43 | L17 | i=2 largest=6 | A child is larger than 2, so swap with the larger child. |
| 44 | L18 | i=2 largest=6 | Swapped arr[2] and arr[6]. Continue sifting from 6. |
| 45 | L14 | i=6 size=7 l=13 r=14 | Sift down from index 6 (2). Its children are at 13 and 14, both outside the heap. |
| 46 | L17 | i=6 largest=6 | 2 is at least as large as both children, so the heap property holds here. |
| 47 | L7 | end=6 | Swap the root with arr[6]. 8 is now in its final position and the heap shrinks to 6 elements. |
| 48 | L14 | i=0 size=6 l=1 r=2 | Sift down from index 0 (2). Its children are at 1 and 2. |
| 49 | L15 | i=0 l=1 largest=0 | The left child is larger. |
| 50 | L16 | i=0 r=2 largest=1 | The right child is larger still. |
| 51 | L17 | i=0 largest=2 | A child is larger than 2, so swap with the larger child. |
| 52 | L18 | i=0 largest=2 | Swapped arr[0] and arr[2]. Continue sifting from 2. |
| 53 | L14 | i=2 size=6 l=5 r=6 | Sift down from index 2 (2). Its children are at 5 and 6; 6 is outside the heap. |
| 54 | L15 | i=2 l=5 largest=2 | The left child is larger. |
| 55 | L17 | i=2 largest=5 | A child is larger than 2, so swap with the larger child. |
| 56 | L18 | i=2 largest=5 | Swapped arr[2] and arr[5]. Continue sifting from 5. |
| 57 | L14 | i=5 size=6 l=11 r=12 | Sift down from index 5 (2). Its children are at 11 and 12, both outside the heap. |
| 58 | L17 | i=5 largest=5 | 2 is at least as large as both children, so the heap property holds here. |
| 59 | L7 | end=5 | Swap the root with arr[5]. 7 is now in its final position and the heap shrinks to 5 elements. |
| 60 | L14 | i=0 size=5 l=1 r=2 | Sift down from index 0 (2). Its children are at 1 and 2. |
| 61 | L15 | i=0 l=1 largest=0 | The left child is larger. |
| 62 | L16 | i=0 r=2 largest=1 | The right child is not larger. |
| 63 | L17 | i=0 largest=1 | A child is larger than 2, so swap with the larger child. |
| 64 | L18 | i=0 largest=1 | Swapped arr[0] and arr[1]. Continue sifting from 1. |
| 65 | L14 | i=1 size=5 l=3 r=4 | Sift down from index 1 (2). Its children are at 3 and 4. |
| 66 | L15 | i=1 l=3 largest=1 | The left child is larger. |
| 67 | L16 | i=1 r=4 largest=3 | The right child is not larger. |
| 68 | L17 | i=1 largest=3 | A child is larger than 2, so swap with the larger child. |
| 69 | L18 | i=1 largest=3 | Swapped arr[1] and arr[3]. Continue sifting from 3. |
| 70 | L14 | i=3 size=5 l=7 r=8 | Sift down from index 3 (2). Its children are at 7 and 8, both outside the heap. |
| 71 | L17 | i=3 largest=3 | 2 is at least as large as both children, so the heap property holds here. |
| 72 | L7 | end=4 | Swap the root with arr[4]. 5 is now in its final position and the heap shrinks to 4 elements. |
| 73 | L14 | i=0 size=4 l=1 r=2 | Sift down from index 0 (1). Its children are at 1 and 2. |
| 74 | L15 | i=0 l=1 largest=0 | The left child is larger. |
| 75 | L16 | i=0 r=2 largest=1 | The right child is not larger. |
| 76 | L17 | i=0 largest=1 | A child is larger than 1, so swap with the larger child. |
| 77 | L18 | i=0 largest=1 | Swapped arr[0] and arr[1]. Continue sifting from 1. |
| 78 | L14 | i=1 size=4 l=3 r=4 | Sift down from index 1 (1). Its children are at 3 and 4; 4 is outside the heap. |
| 79 | L15 | i=1 l=3 largest=1 | The left child is larger. |
| 80 | L17 | i=1 largest=3 | A child is larger than 1, so swap with the larger child. |
| 81 | L18 | i=1 largest=3 | Swapped arr[1] and arr[3]. Continue sifting from 3. |
| 82 | L14 | i=3 size=4 l=7 r=8 | Sift down from index 3 (1). Its children are at 7 and 8, both outside the heap. |
| 83 | L17 | i=3 largest=3 | 1 is at least as large as both children, so the heap property holds here. |
| 84 | L7 | end=3 | Swap the root with arr[3]. 4 is now in its final position and the heap shrinks to 3 elements. |
| 85 | L14 | i=0 size=3 l=1 r=2 | Sift down from index 0 (1). Its children are at 1 and 2. |
| 86 | L15 | i=0 l=1 largest=0 | The left child is larger. |
| 87 | L16 | i=0 r=2 largest=1 | The right child is larger still. |
| 88 | L17 | i=0 largest=2 | A child is larger than 1, so swap with the larger child. |
| 89 | L18 | i=0 largest=2 | Swapped arr[0] and arr[2]. Continue sifting from 2. |
| 90 | L14 | i=2 size=3 l=5 r=6 | Sift down from index 2 (1). Its children are at 5 and 6, both outside the heap. |
| 91 | L17 | i=2 largest=2 | 1 is at least as large as both children, so the heap property holds here. |
| 92 | L7 | end=2 | Swap the root with arr[2]. 3 is now in its final position and the heap shrinks to 2 elements. |
| 93 | L14 | i=0 size=2 l=1 r=2 | Sift down from index 0 (1). Its children are at 1 and 2; 2 is outside the heap. |
| 94 | L15 | i=0 l=1 largest=0 | The left child is larger. |
| 95 | L17 | i=0 largest=1 | A child is larger than 1, so swap with the larger child. |
| 96 | L18 | i=0 largest=1 | Swapped arr[0] and arr[1]. Continue sifting from 1. |
| 97 | L14 | i=1 size=2 l=3 r=4 | Sift down from index 1 (1). Its children are at 3 and 4, both outside the heap. |
| 98 | L17 | i=1 largest=1 | 1 is at least as large as both children, so the heap property holds here. |
| 99 | L7 | end=1 | Swap the root with arr[1]. 2 is now in its final position and the heap shrinks to 1 element. |
| 100 | L14 | i=0 size=1 l=1 r=2 | Sift down from index 0 (1). Its children are at 1 and 2, both outside the heap. |
| 101 | L17 | i=0 largest=0 | 1 is at least as large as both children, so the heap property holds here. |
| 102 | L10 | Sorted in place with 27 comparisons and 20 swaps. Equal keys can swap past each other, so heap sort is not stable. |
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).
1static int[] topK(int[] a, int k) { // the k largest values, in descending order2PriorityQueue<Integer> heap = new PriorityQueue<>(); // min-heap: the root is the smallest of the k kept3for (int x : a) {4if (heap.size() < k) heap.add(x);5else if (x > heap.peek()) { heap.poll(); heap.add(x); }6}7int[] out = new int[heap.size()];8for (int i = out.length - 1; i >= 0; i--) out[i] = heap.poll();9return 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 inputThe heap is empty.
int[] arr · length 0
stream
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.
1public static int[] topK(int[] nums, int k) {2PriorityQueue<Integer> heap = new PriorityQueue<>(); // min-heap3for (int x : nums) {4if (heap.size() < k) {5heap.add(x);6} else if (x > heap.peek()) {7heap.poll();8heap.add(x);9}10}11return heap.stream().mapToInt(i -> i).toArray();12}
Live state
k
3
Execution trace (dry run table)Show 28 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | k=3 | 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. |
| 2 | L4 | x=5 heap.size=0 k=3 | The heap is not full yet, so add 5. |
| 3 | L5 | x=5 | Add 5 at the end, then sift it up. |
| 4 | L4 | x=12 heap.size=1 k=3 | The heap is not full yet, so add 12. |
| 5 | L5 | x=12 | Add 12 at the end, then sift it up. |
| 6 | L5 | child=12 parent=5 | Parent is not larger than child, so the heap property holds. |
| 7 | L4 | x=3 heap.size=2 k=3 | The heap is not full yet, so add 3. |
| 8 | L5 | x=3 | Add 3 at the end, then sift it up. |
| 9 | L5 | child=3 parent=5 | A min-heap needs parent <= child, so swap them (sift up). |
| 10 | L5 | moved=3 | Swapped; 3 moves up. |
| 11 | L6 | x=17 heap.peek()=3 | 17 beats the smallest of the current top 3 (3), so replace it. |
| 12 | L7 | x=17 | poll() removes the old root; add(17) lands at the root and sifts down. |
| 13 | L7 | node=17 smallest=5 | A child is smaller than the node, so swap with the smaller child (sift down). |
| 14 | L7 | Swapped; 5 moves up. | |
| 15 | L7 | node=17 smallest=17 | The new root is not larger than its children. Heap restored. |
| 16 | L6 | x=9 heap.peek()=5 | 9 beats the smallest of the current top 3 (5), so replace it. |
| 17 | L7 | x=9 | poll() removes the old root; add(9) lands at the root and sifts down. |
| 18 | L7 | node=9 smallest=9 | The new root is not larger than its children. Heap restored. |
| 19 | L6 | x=20 heap.peek()=9 | 20 beats the smallest of the current top 3 (9), so replace it. |
| 20 | L7 | x=20 | poll() removes the old root; add(20) lands at the root and sifts down. |
| 21 | L7 | node=20 smallest=12 | A child is smaller than the node, so swap with the smaller child (sift down). |
| 22 | L7 | Swapped; 12 moves up. | |
| 23 | L7 | node=20 smallest=20 | The new root is not larger than its children. Heap restored. |
| 24 | L6 | x=1 heap.peek()=12 | 1 does not beat 12, so it cannot be in the top 3. Skip it. |
| 25 | L6 | x=14 heap.peek()=12 | 14 beats the smallest of the current top 3 (12), so replace it. |
| 26 | L7 | x=14 | poll() removes the old root; add(14) lands at the root and sifts down. |
| 27 | L7 | node=14 smallest=14 | The new root is not larger than its children. Heap restored. |
| 28 | L11 | return=[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. |
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.
1static class KthLargest {2private final int k;3private final PriorityQueue<Integer> heap = new PriorityQueue<>(); // the k largest values seen4KthLargest(int k) { this.k = k; }5Integer add(int x) { // returns the k-th largest so far, or null if fewer than k values6if (heap.size() < k) heap.add(x);7else if (x > heap.peek()) { heap.poll(); heap.add(x); }8return 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 inputThe heap is empty.
int[] arr · length 0
stream
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.
1public static int[] topK(int[] nums, int k) {2PriorityQueue<Integer> heap = new PriorityQueue<>(); // min-heap3for (int x : nums) {4if (heap.size() < k) {5heap.add(x);6} else if (x > heap.peek()) {7heap.poll();8heap.add(x);9}10}11return heap.stream().mapToInt(i -> i).toArray();12}
Live state
k
3
Execution trace (dry run table)Show 28 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | k=3 | 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. |
| 2 | L4 | x=5 heap.size=0 k=3 | The heap is not full yet, so add 5. |
| 3 | L5 | x=5 | Add 5 at the end, then sift it up. |
| 4 | L4 | x=12 heap.size=1 k=3 | The heap is not full yet, so add 12. |
| 5 | L5 | x=12 | Add 12 at the end, then sift it up. |
| 6 | L5 | child=12 parent=5 | Parent is not larger than child, so the heap property holds. |
| 7 | L4 | x=3 heap.size=2 k=3 | The heap is not full yet, so add 3. |
| 8 | L5 | x=3 | Add 3 at the end, then sift it up. |
| 9 | L5 | child=3 parent=5 | A min-heap needs parent <= child, so swap them (sift up). |
| 10 | L5 | moved=3 | Swapped; 3 moves up. |
| 11 | L6 | x=17 heap.peek()=3 | 17 beats the smallest of the current top 3 (3), so replace it. |
| 12 | L7 | x=17 | poll() removes the old root; add(17) lands at the root and sifts down. |
| 13 | L7 | node=17 smallest=5 | A child is smaller than the node, so swap with the smaller child (sift down). |
| 14 | L7 | Swapped; 5 moves up. | |
| 15 | L7 | node=17 smallest=17 | The new root is not larger than its children. Heap restored. |
| 16 | L6 | x=9 heap.peek()=5 | 9 beats the smallest of the current top 3 (5), so replace it. |
| 17 | L7 | x=9 | poll() removes the old root; add(9) lands at the root and sifts down. |
| 18 | L7 | node=9 smallest=9 | The new root is not larger than its children. Heap restored. |
| 19 | L6 | x=20 heap.peek()=9 | 20 beats the smallest of the current top 3 (9), so replace it. |
| 20 | L7 | x=20 | poll() removes the old root; add(20) lands at the root and sifts down. |
| 21 | L7 | node=20 smallest=12 | A child is smaller than the node, so swap with the smaller child (sift down). |
| 22 | L7 | Swapped; 12 moves up. | |
| 23 | L7 | node=20 smallest=20 | The new root is not larger than its children. Heap restored. |
| 24 | L6 | x=1 heap.peek()=12 | 1 does not beat 12, so it cannot be in the top 3. Skip it. |
| 25 | L6 | x=14 heap.peek()=12 | 14 beats the smallest of the current top 3 (12), so replace it. |
| 26 | L7 | x=14 | poll() removes the old root; add(14) lands at the root and sifts down. |
| 27 | L7 | node=14 smallest=14 | The new root is not larger than its children. Heap restored. |
| 28 | L11 | return=[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. |
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.
1static List<Integer> mergeK(int[][] lists) {2PriorityQueue<int[]> heap = new PriorityQueue<>(Comparator.comparingInt(e -> e[0])); // {value, list, index}3for (int i = 0; i < lists.length; i++) if (lists[i].length > 0) heap.add(new int[]{lists[i][0], i, 0});4List<Integer> out = new ArrayList<>();5while (!heap.isEmpty()) {6int[] top = heap.poll(); // the smallest head7out.add(top[0]);8int next = top[2] + 1;9if (next < lists[top[1]].length) heap.add(new int[]{lists[top[1]][next], top[1], next});10}11return 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 inputrun 0
run 1
run 2
out[]
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.
1public static int[] kWayMerge(int[][] runs) {2int k = runs.length, total = 0;3for (int[] r : runs) total += r.length;4int[] pos = new int[k]; // next unread index in each run5int[] out = new int[total];6for (int o = 0; o < total; o++) {7int best = -1;8for (int r = 0; r < k; r++) {9if (pos[r] < runs[r].length &&10(best == -1 || runs[r][pos[r]] < runs[best][pos[best]])) {11best = r;12}13}14out[o] = runs[best][pos[best]++];15}16return out;17}
Live state
o
0
pos[0]
0
pos[1]
0
pos[2]
0
Execution trace (dry run table)Show 38 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | o=0 pos[0]=0 pos[1]=0 pos[2]=0 | 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. |
| 2 | L11 | o=0 pos[0]=0 pos[1]=0 pos[2]=0 best=0 r=0 | Run 0's head 7 is the first candidate. |
| 3 | L10 | o=0 pos[0]=0 pos[1]=0 pos[2]=0 best=0 r=1 | Run 1's head is smaller, so it becomes the new best. |
| 4 | L10 | o=0 pos[0]=0 pos[1]=0 pos[2]=0 best=1 r=2 | Run 1's head is still the smallest. |
| 5 | L14 | o=1 pos[0]=0 pos[1]=1 pos[2]=0 best=1 | out[0] = 2, taken from run 1. |
| 6 | L11 | o=1 pos[0]=0 pos[1]=1 pos[2]=0 best=0 r=0 | Run 0's head 7 is the first candidate. |
| 7 | L10 | o=1 pos[0]=0 pos[1]=1 pos[2]=0 best=0 r=1 | Run 0's head is still the smallest. |
| 8 | L10 | o=1 pos[0]=0 pos[1]=1 pos[2]=0 best=0 r=2 | Run 2's head is smaller, so it becomes the new best. |
| 9 | L14 | o=2 pos[0]=0 pos[1]=1 pos[2]=1 best=2 | out[1] = 5, taken from run 2. |
| 10 | L11 | o=2 pos[0]=0 pos[1]=1 pos[2]=1 best=0 r=0 | Run 0's head 7 is the first candidate. |
| 11 | L10 | o=2 pos[0]=0 pos[1]=1 pos[2]=1 best=0 r=1 | Run 0's head is still the smallest. |
| 12 | L10 | o=2 pos[0]=0 pos[1]=1 pos[2]=1 best=0 r=2 | Run 0's head is still the smallest. |
| 13 | L14 | o=3 pos[0]=1 pos[1]=1 pos[2]=1 best=0 | out[2] = 7, taken from run 0. |
| 14 | L11 | o=3 pos[0]=1 pos[1]=1 pos[2]=1 best=0 r=0 | Run 0's head 19 is the first candidate. |
| 15 | L10 | o=3 pos[0]=1 pos[1]=1 pos[2]=1 best=0 r=1 | Run 1's head is smaller, so it becomes the new best. |
| 16 | L10 | o=3 pos[0]=1 pos[1]=1 pos[2]=1 best=1 r=2 | Run 1's head is still the smallest. |
| 17 | L14 | o=4 pos[0]=1 pos[1]=2 pos[2]=1 best=1 | out[3] = 11, taken from run 1. |
| 18 | L11 | o=4 pos[0]=1 pos[1]=2 pos[2]=1 best=0 r=0 | Run 0's head 19 is the first candidate. |
| 19 | L10 | o=4 pos[0]=1 pos[1]=2 pos[2]=1 best=0 r=1 | Run 0's head is still the smallest. |
| 20 | L10 | o=4 pos[0]=1 pos[1]=2 pos[2]=1 best=0 r=2 | Run 0's head is still the smallest. |
| 21 | L14 | o=5 pos[0]=2 pos[1]=2 pos[2]=1 best=0 | out[4] = 19, taken from run 0. |
| 22 | L11 | o=5 pos[0]=2 pos[1]=2 pos[2]=1 best=0 r=0 | Run 0's head 34 is the first candidate. |
| 23 | L10 | o=5 pos[0]=2 pos[1]=2 pos[2]=1 best=0 r=1 | Run 1's head is smaller, so it becomes the new best. |
| 24 | L10 | o=5 pos[0]=2 pos[1]=2 pos[2]=1 best=1 r=2 | Run 2's head is smaller, so it becomes the new best. |
| 25 | L14 | o=6 pos[0]=2 pos[1]=2 pos[2]=2 best=2 | out[5] = 23, taken from run 2. |
| 26 | L11 | o=6 pos[0]=2 pos[1]=2 pos[2]=2 best=0 r=0 | Run 0's head 34 is the first candidate. |
| 27 | L10 | o=6 pos[0]=2 pos[1]=2 pos[2]=2 best=0 r=1 | Run 1's head is smaller, so it becomes the new best. |
| 28 | L10 | o=6 pos[0]=2 pos[1]=2 pos[2]=2 best=1 r=2 | Run 1's head is still the smallest. |
| 29 | L14 | o=7 pos[0]=2 pos[1]=3 pos[2]=2 best=1 | out[6] = 28, taken from run 1. |
| 30 | L11 | o=7 pos[0]=2 pos[1]=3 pos[2]=2 best=0 r=0 | Run 0's head 34 is the first candidate. |
| 31 | L9 | o=7 pos[0]=2 pos[1]=3 pos[2]=2 best=0 r=1 | Run 1 is exhausted, skip it. |
| 32 | L10 | o=7 pos[0]=2 pos[1]=3 pos[2]=2 best=0 r=2 | Run 0's head is still the smallest. |
| 33 | L14 | o=8 pos[0]=3 pos[1]=3 pos[2]=2 best=0 | out[7] = 34, taken from run 0. |
| 34 | L9 | o=8 pos[0]=3 pos[1]=3 pos[2]=2 r=0 | Run 0 is exhausted, skip it. |
| 35 | L9 | o=8 pos[0]=3 pos[1]=3 pos[2]=2 r=1 | Run 1 is exhausted, skip it. |
| 36 | L11 | o=8 pos[0]=3 pos[1]=3 pos[2]=2 best=2 r=2 | Run 2's head 40 is the first candidate. |
| 37 | L14 | o=9 pos[0]=3 pos[1]=3 pos[2]=3 best=2 | out[8] = 40, taken from run 2. |
| 38 | L16 | return=[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).