Heaps & Priority Queues
Always know the smallest (or largest) thing — and update it in O(log n).
▶ guided lesson⚡ 1-minute summary↻ 14 recall cards
before this: Tree Recursion · Arrays & Strings · Sorting
01 · why it exists
Why this exists, and the weight it carries
since1964
Binary heap: sift up and sift down
J. W. J. Williams
The binary heap arrived in June 1964, inside J. W. J. Williams's Algorithm 232: Heapsort in Communications of the ACM.
It keeps a tree in a plain array with a single rule — every parent ≤ its children — so the minimum is always the first element (A[1] in his ALGOL, index 0 in Java).
The Java Toolkit chapter's PriorityQueue card tells how this structure became java.util.PriorityQueue in Java 5.
This card is about the two moves that make it work: sift up and sift down.
the problem it solvedTaking the smallest item again and again while new items keep arriving — O(log n) per step instead of re-sorting everything.
its jobs today
java.util.PriorityQueueis exactly this array heap: its source comment says the two children ofqueue[n]arequeue[2*n+1]andqueue[2*(n+1)].- Java's
ScheduledThreadPoolExecutorkeeps delayed tasks in a heap-based queue, so the next task due always sits on top. - Dijkstra's shortest paths (the Shortest Paths chapter) pulls the closest unsettled place from a min-heap at every step.
the weight it carries
Illustrative: a delivery app keeps 10 lakh (1,000,000) pending orders by promised time. The heap has ⌊log₂ 1,000,000⌋ + 1 = 20 levels, so an offer or poll does at most 19 swaps (a poll compares about 2 × 19 = 38 times). Re-sorting after every new order would cost about 1,000,000 × 20 = 2 crore comparisons each time.
without it
"Give me the next most urgent item" would mean a full scan or a re-sort on every call — O(n) or O(n log n) instead of O(log n).
when · why · how
- when
- You repeatedly need the current min (or max) while items are added and removed.
- why
- offer and poll walk one path of height ⌊log₂ n⌋ → O(log n); peek is O(1). The price: there's no full order, so
containsandremove(x)are O(n). - how
PriorityQueue<Integer> pq = new PriorityQueue<>();(min-heap) ornew PriorityQueue<>(Comparator.reverseOrder())(max-heap); thenoffer,poll,peek.
since1964
Building a heap in O(n) (Floyd's heapify)
Robert W. Floyd
Williams built his heap by inserting the items one at a time — O(n log n) for n items.
In December 1964, Robert W. Floyd's Algorithm 245: Treesort 3 (CACM) built it the other way round: sift down every parent, from the last one back to the root, merging small heaps into bigger ones.
Most nodes sit near the bottom and barely move, so the whole build is linear.
OpenJDK's PriorityQueue.heapify() still says so in a comment: the classic algorithm due to Floyd (1964), known to be O(size).
the problem it solvedTurning n unordered values into a heap in O(n) instead of O(n log n) — the first phase of heapsort and of new PriorityQueue<>(collection).
its jobs today
new PriorityQueue<>(collection)callsheapify(): Floyd's bottom-up sift-downs, starting at index n/2 − 1.- Heapsort's first phase: turn the whole array into a max-heap in O(n) before extracting.
- Bulk-loading a scheduler or a top-k structure with a batch of known items, instead of n separate offers.
the weight it carries
Illustrative: heapify 10 lakh (1,000,000) values. Floyd's sift-downs move at most n/4·1 + n/8·2 + n/16·3 + … < n = 10 lakh swaps. Offering them one by one into a min-heap when they arrive in descending order makes each value climb to its depth — about 18 levels on average — so roughly 1.8 crore swaps.
without it
Every heap built from existing data would pay O(n log n) — heapsort and bulk-loaded queues would be about log n times slower to start.
when · why · how
- when
- You have all n values up front and need a heap of them.
- why
- A node's sift-down cost is its height, and most nodes are near the bottom: the sum of heights is under n, so O(n) — versus O(n log n) for n offers.
- how
PriorityQueue<Integer> pq = new PriorityQueue<>(list);— or by hand:for (int i = n / 2 - 1; i >= 0; i--) siftDown(a, i, n);
since1964
Heapsort
J. W. J. Williams · Robert W. Floyd
Williams published Heapsort (Algorithm 232) in June 1964: build a heap, then take the extreme value out again and again. Later that year Floyd's Treesort 3 sorted in place, inside the same array, with the O(n) bottom-up build. NIST's Dictionary of Algorithms and Data Structures puts it this way: Williams clearly stated the idea, and Floyd gave a complete, efficient implementation nearly identical to what is used today.
the problem it solvedSorting with a guaranteed O(n log n) worst case and only O(1) extra memory.
its jobs today
- The Linux kernel's general-purpose
sort()(lib/sort.c) is a heapsort: non-recursive, O(n log n) even in the worst case, no extra memory. - OpenJDK 17's
DualPivotQuicksort(behindArrays.sort(int[])) switches a range to heap sort when the recursion gets too deep, so a nasty input can't push it to O(n²).
the weight it carries
Illustrative: sort 10 lakh (1,000,000) ints. Heapsort: about 2 × 1,000,000 × 20 = 4 crore comparisons even in the worst case, and no extra array. Merge sort needs a second buffer of 1,000,000 ints (≈ 4 MB); a quicksort without guards can hit n²/2 = 5 × 10¹¹ comparisons on a bad input.
without it
You'd have to choose between O(n) extra memory (merge sort) and an O(n²) worst case (plain quicksort) — kernels and library safety nets can afford neither.
when · why · how
- when
- You need a worst-case O(n log n) sort with O(1) extra space, or a safety net under quicksort.
- why
- O(n) build + n sift-downs of O(log n). Not stable, and it jumps around memory (poor cache use), so it's slower than quicksort on average.
- how
- Build a max-heap in place, then repeatedly swap
a[0]witha[end]and sift down the shrunken heap. In real code just callArrays.sort.
since?
Top-k with a size-k heap
No single inventor is credited: keeping the best k in a bounded heap is folklore that grew out of priority queues and selection. The effort here goes into when and why to use it.
its jobs today
- Trending lists: the 10 most-searched terms out of a never-ending stream, using memory for just 10 items.
- Monitoring: the 100 slowest API calls of the last hour, or the heaviest users of a server.
- Ranking: keep only the best k scored candidates (search results, recommendations) instead of sorting all of them.
the weight it carries
Illustrative: 10 crore (100,000,000) search events, k = 10. Size-k heap: at most one offer and one poll on a heap of 11 (height 3) — under 10 comparisons per event → under 100 crore, while holding just 10 items. Sorting everything: 10⁸ × log₂ 10⁸ ≈ 10⁸ × 27 = 270 crore comparisons, with all 10 crore events in memory.
without it
Every "top 10" would sort or store the whole stream — impossible when the stream never ends.
when · why · how
- when
- "k largest / smallest / closest / most frequent", especially for streams or when k is much smaller than n.
- why
- O(n log k) time and O(k) memory versus O(n log n) and O(n) for sorting. Quickselect is O(n) on average but needs all the data in memory.
- how
- k largest → min-heap:
pq.offer(x); if (pq.size() > k) pq.poll();— k smallest → the same withnew PriorityQueue<>(Comparator.reverseOrder()).
since?
Two heaps for a running median
No single inventor is credited: splitting the data into a max-heap lower half and a min-heap upper half is a classic textbook exercise and interview pattern.
its jobs today
- Live dashboards: the median delivery time or median API latency, updated as each new order or request arrives.
- Percentile trackers: the same split with uneven sizes gives any running percentile (keep 90% of the values in the lower heap for p90).
the weight it carries
Illustrative: 10 lakh latency readings, with the median wanted after every one. Two heaps: at most 5 heap operations × about 20 levels ≈ 100 steps per reading → about 10 crore in total. A sorted ArrayList: the binary search is quick, but the inserts shift about n²/4 = 2.5 × 10¹¹ elements overall.
without it
Each new reading would need a re-sort or an O(n) insert, and a live median would fall behind the data.
when · why · how
- when
- The median (or a percentile) of numbers that keep arriving; any "balance a lower half against an upper half" problem.
- why
- addNum is O(log n) and findMedian is O(1): the two middle candidates always sit on the tops. Cost: O(n) memory for all the numbers.
- how
low = new PriorityQueue<>(Comparator.reverseOrder()); high = new PriorityQueue<>();— offer to low, move low's max to high, move high's min back if high gets bigger.
since?
K-way merge with a min-heap
Merging many sorted runs at once is as old as sorting data on magnetic tape, and Knuth's The Art of Computer Programming, Vol. 3 treats multiway merging under external sorting — but no single inventor is credited for picking the next item with a heap.
its jobs today
- External sorting: a database sorts a table bigger than RAM by sorting chunks, writing them out, then merging many sorted runs at once (in a single pass when there are few enough).
- Log tools merge sorted log files from many servers into one timeline by timestamp.
- News feeds: merge the newest posts of every account you follow, newest first (Design Twitter in this chapter).
the weight it carries
Illustrative: 100 sorted log files from 100 servers, 1 lakh lines each = 1 crore lines. A heap of 100 cursors: each line costs about log₂ 100 ≈ 6.6 levels → about 6.6 crore steps. Merging the files one after another re-walks the growing result: 1 lakh × (2 + 3 + … + 100) = 1 lakh × 5,049 ≈ 50 crore steps — over 7× more.
without it
Merging many sorted sources would mean checking all k heads for every item (O(N·k)) or re-sorting everything (O(N log N)).
when · why · how
- when
- k sorted lists, arrays, files or feeds; pairs from two sorted arrays in sum order (each row is a sorted list); the smallest range covering k lists.
- why
- Each of the N items enters and leaves a heap of at most k cursors: O(N log k) time, O(k) extra space.
- how
PriorityQueue<int[]> heap = new PriorityQueue<>(Comparator.comparingInt(c -> c[0]));with cursors{value, list, index}— poll the smallest, then offer that list's next element.
since?
Greedy with a heap
A habit, not an invention: many greedy algorithms (Huffman coding, Dijkstra, Prim) are usually implemented by pulling the next step from a priority queue, but nobody is credited with the general pattern.
its jobs today
- Schedulers: always run the task with the most work left or the highest priority next (Task Scheduler in this chapter).
- Compression: Huffman coding repeatedly merges the two rarest symbols, pulled from a min-heap.
- Budgeted choices: with the money you have now, take the most profitable affordable option, then check what it unlocked (IPO in this chapter).
the weight it carries
Illustrative: IPO with 1 lakh projects and k = 1 lakh rounds. Scanning every project each round: 10⁵ × 10⁵ = 10¹⁰ checks. Sort once + max-heap: 10⁵ × log₂ 10⁵ ≈ 17 lakh steps for the sort, plus 2 × 10⁵ heap operations × ~17 levels ≈ 34 lakh — about 51 lakh in total, roughly 2,000× less.
without it
Each greedy step would rescan all candidates — O(n) per step, O(n·k) overall.
when · why · how
- when
- "Repeatedly take the best / largest / cheapest", "the two largest", schedules with cooldowns, and choices that unlock over time.
- why
- The heap hands over the current best in O(log n) after every change. It doesn't make the greedy correct — you still need a reason the choice is safe (an exchange argument).
- how
- Max-heap of counts or profits:
new PriorityQueue<>(Comparator.reverseOrder()). For unlockable items, sort by the unlock key once and move a pointer forward, offering items as they unlock.
02 · plain english
The idea in 30 seconds
A heap is a complete binary tree where every parent is ≤ its children (min-heap). So the minimum is always on top.
It isn't fully sorted — only parent vs child is ordered. That weaker promise is what makes insert and remove-min cheap: O(log n).
It lives in a plain array: the children of index i are 2i+1 and 2i+2, the parent is (i−1)/2. Java's PriorityQueue is exactly this.
picture this
Think of the scheduler behind Task Manager: dozens of processes are waiting, and the CPU always picks the most urgent one next. When a new process arrives, it is slotted in with a few comparisons — nobody re-sorts the whole waiting list.
03 · remember
What you must know
- Heap property: min-heap → every parent ≤ its children (max-heap: ≥). Siblings have no order, so the array is not sorted.
- Shape property: the tree is complete — every level full except the last, which fills left to right. That's why it fits in an array with no gaps and has height ⌊log₂ n⌋.
- Index math (0-based): parent(i) = (i − 1) / 2, left(i) = 2i + 1, right(i) = 2i + 2.
- offer = append + sift up: swap with the parent while smaller. poll = take root, move the last leaf to the root, sift down: swap with the smaller child while bigger. Both walk one path: O(log n). peek is O(1).
- Build a heap in O(n) (heapify): sift down every parent from the last one up to the root. Most nodes are near the bottom and barely move, so the total work is linear — not n log n.
- `PriorityQueue` is a min-heap by default. Max-heap:
new PriorityQueue<>(Comparator.reverseOrder()).remove(Object)andcontainsare O(n) — a heap can't find an arbitrary element fast. - Top-k pattern: k largest → keep a min-heap of size k (the root is the weakest of the winners and gets kicked out). k smallest → max-heap of size k. O(n log k).
- Two heaps (max-heap for the lower half, min-heap for the upper half) give a running median; k-way merge keeps one cursor per sorted list in a heap — also for "rows" you never build, like the sum table in Find K Pairs.
- Greedy with a heap: keep the candidates in a heap and act on the best one now (heaviest stone, biggest affordable profit). When candidates unlock over time, sort them by the unlock key once and push them in as they unlock.
04 · see it
Play with it in 3D
Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.
offer — append and sift up
Tree and array are the same data. Watch a value bubble up while it's smaller than its parent at (i−1)/2.
poll — take the root, sift down
The last leaf jumps to the root to keep the tree complete, then sinks past the smaller child.
Heapify in O(n)
Leaves are already heaps (green). Fix each parent from the bottom up — count the swaps against n.
Heap sort in place
One array: a shrinking max-heap on the left, a growing sorted block on the right.
Top-k with a size-k min-heap
The root is the bouncer: anything smaller than it never gets into the top k.
Running median with two heaps
A max-heap holds the smaller half, a min-heap the larger half. The middle values always sit on the two tops.
K-way merge — one candidate per list
The heap never holds more than one node per list. Poll the smallest, append it, offer that list's next node.
Greedy with a heap — IPO
Projects unlock as capital grows (sorted by capital); the max-heap always hands over the biggest profit you can afford.
05 · cost
Time & space, explained
| Time | Why it's that fast (or slow) | why | |
|---|---|---|---|
| peek (min) | O(1) | Read index 0 | The heap property puts the minimum at the root. |
| offer / add | O(log n) | Sift up ≤ height | The new value climbs at most ⌊log₂ n⌋ levels (amortized: the array occasionally grows). |
| poll / remove() | O(log n) | Sift down ≤ height | The moved leaf sinks at most ⌊log₂ n⌋ levels. |
Build from n values (heapify / new PriorityQueue<>(collection)) | O(n) | Σ heights ≤ n | n/2 leaves do no work, n/4 nodes sink ≤ 1 level, n/8 sink ≤ 2… the series sums to less than n. |
| n separate offers | O(n log n) | Each is O(log n) | Building one-by-one is slower than heapify (still fine in practice). |
| remove(Object) / contains | O(n) | Linear scan | Siblings aren't ordered, so finding an arbitrary value means checking everything. |
| Heap sort | O(n log n) | In place, O(1) extra | Heapify O(n) + n polls of O(log n). Worst case too — but not stable. |
| Iterate / toString | O(n) | Array order, NOT sorted | Only polling repeatedly gives sorted order. |
| Time | Extra space | why | |
|---|---|---|---|
| Sort everything | O(n log n) | O(log n) – O(n) | Simple, but does far more work than needed when k is small. |
| Size-k min-heap | O(n log k) | O(k) | Each element costs O(log k); works on streams that never end. |
| Heapify all, then poll k times | O(n + k log n) | O(n) | Linear build, then k cheap pops. |
| Quickselect (random pivot) | O(n) average, O(n²) worst | O(1) in place | Partition and keep only the side with the answer: n + n/2 + n/4 + … ≈ 2n. |
| Counting / buckets (bounded values) | O(n + range) | O(range) | No comparisons at all when values or frequencies are small integers. |
| Time | Extra space | why | |
|---|---|---|---|
| Two heaps: addNum / findMedian | O(log n) / O(1) | O(n) | At most five heap operations per add; the median is read from the two tops. |
| K-way merge of N items from k sorted lists | O(N log k) | O(k) | Every item enters and leaves a heap that holds one cursor per list. |
| K smallest pair sums (Find K Pairs) | O(k log min(n₁, k)) | O(min(n₁, k)) | k polls on a heap of row cursors — the n₁·n₂ sum table is never built. |
| Sort + heap greedy (IPO) | O(n log n + k log n) | O(n) | One sort to unlock in order; each project enters and leaves the heap at most once. |
06 · java
Raw vs Collections Framework
A heap by hand vs PriorityQueue
/** A binary min-heap of ints stored in an array: children of i are 2i+1 and 2i+2, parent is (i-1)/2. */class MinHeap { private int[] a = new int[16]; private int size = 0; void offer(int x) { if (size == a.length) a = Arrays.copyOf(a, size * 2); // grow like ArrayList a[size] = x; // new value goes in the next free slot siftUp(size++); } private void siftUp(int i) { while (i > 0) { int parent = (i - 1) / 2; if (a[parent] <= a[i]) break; // order holds: stop swap(i, parent); // smaller value bubbles up i = parent; } } int peek() { if (size == 0) throw new NoSuchElementException(); return a[0]; // the minimum is always the root } int poll() { if (size == 0) throw new NoSuchElementException(); int top = a[0]; a[0] = a[--size]; // last leaf jumps to the root siftDown(0); return top; } private void siftDown(int i) { while (true) { int left = 2 * i + 1, right = left + 1, smallest = i; if (left < size && a[left] < a[smallest]) smallest = left; if (right < size && a[right] < a[smallest]) smallest = right; if (smallest == i) return; // both children are bigger: stop swap(i, smallest); // larger value sinks i = smallest; } } /** Floyd's build-heap: sift down every parent, last parent first. O(n), not O(n log n). */ static MinHeap heapify(int[] values) { MinHeap h = new MinHeap(); h.a = Arrays.copyOf(values, Math.max(16, values.length)); h.size = values.length; for (int i = h.size / 2 - 1; i >= 0; i--) h.siftDown(i); // leaves are already heaps return h; } int size() { return size; } private void swap(int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; }}class PriorityQueueApi { static void demo(List<String> log) { PriorityQueue<Integer> min = new PriorityQueue<>(); // min-heap by default PriorityQueue<Integer> max = new PriorityQueue<>(Comparator.reverseOrder()); // max-heap for (int x : new int[] {5, 1, 8, 3}) { min.offer(x); max.offer(x); } // O(log n) each log.add("min.peek=" + min.peek() + " max.peek=" + max.peek()); // O(1) log.add("toString is NOT sorted: " + min); // array order, not sorted order // compare by a key: here [x, y] points by distance², closest first PriorityQueue<int[]> byDist = new PriorityQueue<>( Comparator.comparingInt(p -> p[0] * p[0] + p[1] * p[1])); byDist.offer(new int[] {3, 4}); byDist.offer(new int[] {1, 1}); log.add("closest=" + Arrays.toString(byDist.poll())); // never write (a, b) -> a - b: it overflows for large values PriorityQueue<Integer> safe = new PriorityQueue<>((a, b) -> Integer.compare(b, a)); safe.addAll(List.of(Integer.MIN_VALUE, Integer.MAX_VALUE)); log.add("safe max=" + safe.peek()); log.add("remove(8)=" + min.remove(8) + " contains(8)=" + min.contains(8)); // O(n) linear scans StringBuilder order = new StringBuilder(); while (!min.isEmpty()) order.append(min.poll()).append(' '); // poll gives sorted order log.add("polled=" + order.toString().trim()); log.add("poll on empty=" + min.poll()); // null, no exception // the collection constructor heapifies in O(n) PriorityQueue<Integer> built = new PriorityQueue<>(List.of(9, 4, 7, 1)); log.add("built.peek=" + built.peek()); }}PriorityQueueis a binary min-heap in anObject[]— the same sift-up/sift-down you see on the left.- Max-heap:
new PriorityQueue<>(Comparator.reverseOrder())or(a, b) -> Integer.compare(b, a). Never(a, b) -> b - a: it overflows for large values and silently mis-orders. - Order objects by a key with
Comparator.comparingInt(...), add tie-breakers with.thenComparing(...). - Don't change an object's priority while it sits in the queue — the heap won't notice. Remove and re-add it, or push a new entry and skip stale ones when polled ("lazy deletion").
poll()/peek()returnnullon an empty queue;remove()/element()throw.PriorityQueuerejectsnullelements and isn't thread-safe (usePriorityBlockingQueue).
Heap sort
class HeapSort { /** In-place, O(n log n) worst case, O(1) extra space, not stable. */ static void sort(int[] a) { int n = a.length; for (int i = n / 2 - 1; i >= 0; i--) siftDown(a, i, n); // build a MAX-heap for (int end = n - 1; end > 0; end--) { swap(a, 0, end); // max goes to its final slot siftDown(a, 0, end); // repair the shrunken heap } } private static void siftDown(int[] a, int i, int n) { while (true) { int largest = i, left = 2 * i + 1, right = left + 1; if (left < n && a[left] > a[largest]) largest = left; if (right < n && a[right] > a[largest]) largest = right; if (largest == i) return; swap(a, i, largest); i = largest; } } private static void swap(int[] a, int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; }}- Ascending order uses a max-heap: the largest goes to the end, then the heap shrinks by one.
- Guaranteed O(n log n) with O(1) extra space — but not stable and cache-unfriendly, so
Arrays.sort(dual-pivot quicksort / TimSort) is faster in practice.
Which heap for top-k?
class Solution { public int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> heap = new PriorityQueue<>(); // min-heap of the k largest so far for (int x : nums) { heap.offer(x); if (heap.size() > k) heap.poll(); // drop the smallest of k+1 } return heap.peek(); // smallest of the top k }}- It feels backwards: to keep the k largest, use a min-heap. Its root is the smallest of your winners — exactly the one to evict when something better arrives.
- To keep the k smallest (closest points, cheapest items), use a max-heap of size k.
- If you need all n elements in order anyway, a heap is overkill — sort.
Cursors in a heap: int[] entries and two-key comparators
class Solution { public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) { // a cursor {i, j} stands for the pair (nums1[i], nums2[j]); smallest sum on top. // comparingInt, never (a, b) -> sumA - sumB: two sums can differ by 4·10⁹ and overflow PriorityQueue<int[]> heap = new PriorityQueue<>(Comparator.comparingInt(c -> nums1[c[0]] + nums2[c[1]])); for (int i = 0; i < Math.min(nums1.length, k); i++) heap.offer(new int[] {i, 0}); // head of every row List<List<Integer>> result = new ArrayList<>(); while (result.size() < k && !heap.isEmpty()) { int[] c = heap.poll(); // smallest sum not taken yet result.add(List.of(nums1[c[0]], nums2[c[1]])); if (c[1] + 1 < nums2.length) heap.offer(new int[] {c[0], c[1] + 1}); // next pair in the same row } return result; }}- A heap entry can be a small
int[]cursor like{i, j}or{value, list, index}— the comparator decides what "smaller" means. Comparator.comparingInt(c -> nums1[c[0]] + nums2[c[1]])is overflow-safe as long as the key itself fits in an int. Subtracting two keys is not: two sums can differ by 4·10⁹.- Two keys:
Comparator.comparingInt((String w) -> count.get(w)).thenComparing(Comparator.reverseOrder())— count ascending, then word Z → A (the weakest word on top in Top K Frequent Words).
| call | does | cost |
|---|---|---|
new PriorityQueue<>() | Empty min-heap (natural order) | O(1) |
new PriorityQueue<>(Comparator.reverseOrder()) | Max-heap | O(1) |
new PriorityQueue<>(Comparator.comparingInt(x -> key)) | Order objects/arrays by a key | O(1) |
new PriorityQueue<>(collection) | Heapify existing values | O(n) |
pq.offer(x) / pq.add(x) | Insert | O(log n) |
pq.poll() | Remove and return the min (null if empty) | O(log n) |
pq.peek() | Look at the min (null if empty) | O(1) |
pq.size() / isEmpty() | Count | O(1) |
pq.remove(x) / contains(x) | Remove / find an arbitrary element | O(n) |
pq.addAll(list) | Offers one by one | O(m log n) |
Repeatedly take the smallest/largest while adding new items
→ PriorityQueue
O(log n) offer/poll, O(1) peek.
k largest / k smallest of a big or endless stream
→ Size-k heap (opposite direction)
O(n log k) time, O(k) memory.
Running median
→ Two heaps (max-heap low half + min-heap high half)
O(log n) add, O(1) median.
Merge k sorted lists/arrays/streams
→ Min-heap of one cursor per source
O(N log k) — each element passes through a heap of size k.
Options that unlock over time (capital, time, fuel)
→ Sort by the unlock key + a max-heap of unlocked options
Each option is unlocked once and chosen at most once: O(n log n).
Remove arbitrary elements or find floor/ceiling
→ TreeMap / TreeSet
O(log n) for any key; a heap is O(n) for these.
Just the single min or max once
→ One linear scan
O(n), no heap needed.
07 · recognise
Spot the pattern
"k largest", "k smallest", "k closest", "top k frequent"
Size-k heap in the opposite direction (or quickselect / buckets)
"kth largest in a stream", values keep arriving
Size-k min-heap; the root is the answer
"median" of a changing collection
Two heaps, sizes balanced within one
Merge k sorted things
Min-heap of (value, source, index) cursors
"k smallest pairs / sums" from two sorted arrays
Each row of the sum table is sorted → k-way merge over rows
"smallest range" with one element from each of k lists
Heap of one pointer per list + track the max; always advance the min
Always process the most urgent / cheapest / biggest next (greedy)
Priority queue keyed by urgency (task scheduling, Dijkstra, stones)
Choices unlock as a resource grows ("at most k projects", capital, fuel)
Sort by the unlock threshold + max-heap of what's unlocked
"rearrange so no two equal items are adjacent"
Max-heap by count, hold back the last used item one turn
Schedule meetings / rooms by earliest end time
Min-heap of end times (see the Intervals chapter)
08 · traps & tricks
Mistakes to avoid, tricks to use
traps
- Expecting
PriorityQueueto iterate or print in sorted order — onlypollgives sorted order. - Comparator
(a, b) -> a - boverflows (e.g.Integer.MIN_VALUE - 1). UseInteger.compare(a, b)orComparator.comparingInt. - Putting all n values in a max-heap and polling k times for "k largest": correct, but O(n) memory and O(n log n) time when built by offers. A size-k min-heap needs O(n log k) time and O(k) memory.
- Calling
pq.remove(x)inside a loop — each call is O(n), so the loop becomes O(n²). - Mutating a field that the comparator reads while the object is inside the queue — the heap order silently breaks.
- Integer overflow when averaging two medians:
(a + b) / 2.0with ints near 2³¹ — cast tolongfirst. - Using
int[]entries but comparing arrays with==— compare by the field you mean (a[0]). - Re-scanning every candidate each round in a greedy (IPO, task picking) — that's O(n·k). Sort once and let a heap hand you the best.
tricks
- Say "heap" and "O(log n)" in the same breath, then say what's on top and why it's the right thing to have on top.
- For any top-k problem, mention three options: sort (O(n log n)), heap (O(n log k)), quickselect (O(n) average). Pick the heap unless asked to optimize.
- Draw the array and the tree side by side when explaining sift-up/down — interviewers love the index math.
- Store
int[]or a smallrecordin the queue when you need value + source (k-way merge, Dijkstra). - When a problem says "stream" or "design", think about what must be fast: add, query, or both.
- A heap makes a greedy fast, not correct. Say why the greedy choice is safe (an exchange argument) before you code it.
09 · why & how
Questions you might be too shy to ask
Why is a heap stored in an array instead of with TreeNode pointers?
Because it's always a complete tree: no gaps, so position i fully determines the parent and children by arithmetic. That saves the memory of two pointers per node and is very cache-friendly.
Why does building a heap take O(n), not O(n log n)?
Sift-down costs the node's height, not its depth. Half the nodes are leaves (height 0), a quarter have height 1, an eighth height 2… The sum n/4·1 + n/8·2 + n/16·3 + … is less than n. Sift-up-based building (n offers) is O(n log n) because the many bottom nodes may climb far.
How does a comparator decide what comes out first?
compare(a, b) returning a negative number means "a comes out before b".
Integer.compare(a, b) → smaller first → min-heap (the default).
Integer.compare(b, a) → for a = 5, b = 3 it returns negative, so 5 comes out first → max-heap.
Simple hai: whatever your comparator calls "smaller" sits on top.
I printed my PriorityQueue and got [1, 2, 3, 5, 4]. Is it broken?
No. Printing (and a for-each loop) shows the heap's internal array, which is only parent ≤ child, not sorted.
Offer 5, 1, 3, 2, 4 and that is exactly what Java prints — 5 and 4 are both children of 2, and siblings have no order.
Only poll() gives sorted order: while (!pq.isEmpty()) out.add(pq.poll());.
Why does new PriorityQueue<int[]>() crash with ClassCastException?
Without a comparator, the queue assumes the elements are Comparable — like Integer or String.
An int[] isn't, so the queue can't cast it to Comparable and throws — in modern Java (17+) already on the very first offer (old Java 8 waited until the second).
Always pass the order you mean: new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0])).
Heap vs sorted array vs BST — when do I use which?
Heap: you only ever need the min/max, with inserts mixed in (O(log n) each). Sorted array: mostly reads, rare inserts. BST/TreeMap: you need any key, floor/ceiling, or deletions of arbitrary elements in O(log n).
Why a MIN-heap for the k LARGEST elements?
You keep k winners and need to quickly find the weakest winner to compare against newcomers. The weakest of the largest values is the smallest — which a min-heap keeps on top.
Size-k heap: do I offer first and then poll, or check peek() first?
Both are correct. The simple way: pq.offer(x); if (pq.size() > k) pq.poll(); — the extra one always falls out.
The slightly faster way: once the heap is full, offer only if x > pq.peek(), then poll.
In an interview, write the simple version and mention the other. Same O(n log k).
Is PriorityQueue stable — do equal priorities come out in insertion order?
No. Ties come out in an unspecified order. If order matters, add a tie-breaker to the comparator (e.g. an increasing sequence number).
Median: why offer to low and then move its top to high? Isn't that wasted work?
It is a cheap way to keep the rule "every value in low ≤ every value in high" without an if.
The new number goes into low; low's biggest value — maybe the new one — goes to high.
So a big new number ends up in high; a small one stays in low, and low's old biggest moves up instead.
The last step only fixes the sizes. Each step is O(log n), so it's still O(log n) per add.
How do I update the priority of something already in the queue?
Java's PriorityQueue has no decrease-key. Either remove(obj) (O(n)) and re-offer, or simply offer a new entry with the new priority and, when you poll an outdated one, skip it (lazy deletion). Dijkstra in Java usually does the latter.
How do I know a greedy-with-heap idea is actually correct?
The heap only makes the greedy fast. To trust it, use an exchange argument: take any best answer whose first choice differs from yours, swap your choice in, and show the result is no worse. If you can't, the problem may need DP instead — Last Stone Weight II (pick any two stones) is a DP, not a heap.
Should I write my own heap in an interview?
Usually no — use PriorityQueue and say "a binary min-heap, O(log n) offer and poll".
Write one only when asked "implement a heap". Then you need just three things: the array, sift up and sift down.
Knowing the index math (2i + 1, 2i + 2, (i - 1) / 2) is what they check.
What's the difference between a heap and "the heap" in Java memory?
Nothing in common but the name. The JVM heap is the memory area where objects live; the heap data structure is this priority tree.
10 · practice
Interview problems
Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.
- #703
- #1046Last Stone WeightEasyNVIDIAPayPalAmazonSalesforceBloomberg+1
- #215
- #347
- #973
- #621Task SchedulerMediumAmazonTCSOracleAppleMicrosoft+1
- #23
- #295
- #355Design TwitterMediumAdobeSalesforcePayPalAmazonGoogle+1
- #767Reorganize StringMediumAmazonByteDance / TikTokOracleGoogleMicrosoft+1
- #373Find K Pairs with Smallest SumsMediumLinkedInAmazonGoogle
- #502IPOHardSamsungAmazonGoogle
- #632Smallest Range Covering Elements from K ListsHardAmazonGoogle
- #692Top K Frequent WordsMediumUberBloombergAmazonGoogle
Also practise this here
Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.
- #253Meeting Rooms IIMediumin IntervalsLeetCode Premium
Sort meetings by start, keep a min-heap of end times: its size is the number of rooms in use.
- #743Network Delay TimeMediumin Shortest Paths
Dijkstra is a greedy with a heap: always settle the closest unsettled node, skipping stale entries (lazy deletion).
- #912Sort an ArrayMediumin Sorting
Its heap-sort approach is this chapter's in-place heap: build a max-heap in O(n), then move the max to the end n times.
11 · revise
The whole chapter on one screen
9 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.
- what it is
A heap is a complete binary tree in an array: every parent ≤ its children (min-heap). The min sits at index 0; children of i are 2i + 1 and 2i + 2.
- cost
peekO(1) ·offer/pollO(log n) · build from n values O(n) ·remove(x)/containsO(n) · heap sort O(n log n) in O(1) space. - use it when
You need the current min/max while items keep arriving: top-k, k closest, running median, merge k sorted lists, greedy "take the best next".
- template
k largest: min-heap,
pq.offer(x); if (pq.size() > k) pq.poll();— the root is the k-th largest. k smallest: the same with a max-heap. - template
Median: max-heap
low+ min-heaphigh. Offer to low, move low's max to high, move it back if high is bigger. Median = the tops. - template
K-way merge: one cursor per sorted list in a min-heap. Poll the smallest, offer that list's next. O(N log k) time, O(k) space.
- trap
Printing or iterating a
PriorityQueueis NOT sorted — only repeatedpoll()returns values in order. - trap
(a, b) -> a - boverflows for far-apart values and silently mis-orders the heap. UseInteger.compareorComparator.comparingInt. - pro tip
No decrease-key in Java: offer a new entry and skip stale ones when you poll them (lazy deletion), as Dijkstra does.
12 · recall
Flip, remember, repeat
In a 0-indexed array heap, where are the parent and the children of index i?
Question 1: In a 0-indexed array heap, where are the parent and the children of index i?
13 · check yourself
Quick quiz
In a 0-indexed array heap, where are the children of index 4?
Which is a valid min-heap array?
What is the time to build a heap from n unsorted values with bottom-up heapify?
To find the 10 largest values in a stream of a billion numbers with little memory, you use…
What does `System.out.println(pq)` print for a PriorityQueue after offering 5, 1, 8, 3?
Why is `pq.remove(x)` O(n) for an arbitrary x?
Two heaps for the running median: which heap holds the smaller half?
Merging k = 100 sorted lists with 10⁶ nodes in total using a heap: at most how many entries are in the heap at once?
In IPO, why sort the projects by required capital before using the max-heap?
14 · learn more
A second teacher, if you want one
- ▶ video2.6.3 Heap - Heap Sort - Heapify - Priority Queues ↗Abdul Bari · EnglishThe classic whiteboard lesson on heaps: insert, delete, heapify and heap sort, calm and very clear.
- ▶ playlistHeap Playlist | Interview Questions | Coding | Tutorials | Data Structures ↗Aditya Verma · HindiHow to recognise a heap problem (k-th largest, top k, k closest) and whether a min-heap or a max-heap fits.
- ◈ visualizerBinary Heap (Priority Queue) ↗VisuAlgo (Steven Halim, NUS) · EnglishWatch sift-up and sift-down in both the tree view and the array view, and try the O(n) build-heap yourself.