Heaps & Priority Queues — Five Problems
Five heap problems — kth largest in array (size-k min-heap), connect ropes minimum cost (Huffman greedy), kth largest in stream (persistent heap), merge M sorted lists (k-way merge), and check if binary tree is min-heap (DFS + array indexing). Full walkthroughs + commented Java.
Assumes you know
After this you should be able to
- Explain the core idea in Heaps & Priority Queues — Five Problems
- Apply Heaps & Priority Queues — Five Problems in a representative exercise
0. Heap Foundations#
The two properties#
A heap is a complete binary tree obeying one ordering rule at every node.
- Min-heap — every parent ≤ both children. The minimum sits at the root.
- Max-heap — every parent ≥ both children. The maximum sits at the root.
- The rule holds recursively for every node, not just the root.
- Root access is O(1); there is no global left-to-right ordering, only the parent/child relation.
Heapify (the core operation)#
Heapify restores the property by swapping a violating node with a parent/child until the rule holds again. On insert, the new node lands at the bottom and "bubbles up" via swaps; on extract, the root is removed and the property is restored downward.
⭐ TRICK — Heapify walks the height, so it's O(log n). A node inserted at the deepest leaf can, worst case, travel all the way to the root — that path length is the tree height, and a binary tree's height is log₂n. Sorting n elements through a heap is therefore n × O(log n) = O(n log n).
💡 TIP — You rarely code heapify by hand. Java's PriorityQueue heapifies internally on every add/poll. Learn the manual process anyway: interviewers ask you to explain it, even though your solution code just calls the library. [+]
Java PriorityQueue cheatsheet#
PriorityQueue is a min-heap by default. Flip it to a max-heap with a reverse-order comparator.
| Operation | Method | Cost |
|---|---|---|
| Insert element | pq.add(x) | O(log n) |
| Remove root | pq.poll() | O(log n) |
| Peek at root | pq.peek() | O(1) |
| Make a max-heap | new PriorityQueue<>(Collections.reverseOrder()) | — |
⚠ PITFALL — the array-index formulas assume 0-based indexing. For a node at index i: left child = 2i+1, right child = 2i+2. If you (or a source) start indexing at 1, the formulas shift to 2i and 2i+1 — a frequent off-by-one bug. [!] The transcript first guessed i+1 / i+2, then corrected to 2i+1 / 2i+2; the corrected form is right.
k largest → MIN-heap of size k. k smallest → MAX-heap of size k. Get this backwards and your complexity silently degrades to O(n log n).
The one line an interviewer is listening for
I. Kth Largest Element in an Array#
1. Problem#
Heap · Top-KGiven an integer array and a positive integer k (k ≤ n), return the k-th largest element. Not the k largest elements — the single element in the k-th largest position.
Practice: LeetCode 215 — Kth Largest Element in an Arrayleetcode.com/problems/kth-largest-element-in-an-array
2. Brute force → optimal#
Brute force: sort descending, return index k−1 → O(n log n). Correct but wasteful — you paid to fully order every element when you only cared about one position.
⭐ TRICK — keep a size-k MIN-heap, not a max-heap. Scan the array; hold only the k largest seen so far. Because it's a min-heap, its root is the smallest of those k — which is exactly the k-th largest overall. For each new element, if it beats the root, poll the root and add it. Heap never exceeds size k, so each operation is O(log k) → total O(n log k).
⚠ PITFALL — the instinctive choice is a max-heap, and it's wrong. A max-heap of all n elements works but costs O(n log n) and an interviewer will ask you to improve it. The min-heap is the answer they want.
3. Java (optimal)#
public static int findKthLargest(List<Integer> arr, int k) {
if (arr == null || arr.size() < k) System.exit(-1); // invalid input
// min-heap seeded with the first k elements
PriorityQueue<Integer> pq = new PriorityQueue<>(arr.subList(0, k));
for (int i = k; i < arr.size(); i++) {
if (arr.get(i) > pq.peek()) { // bigger than the smallest of our k
pq.poll(); // drop the smallest
pq.add(arr.get(i)); // keep the new, larger one
}
}
return pq.peek(); // root = k-th largest
}Time O(n log k) · Space O(k) — auxiliary space is just the heap, which never exceeds k elements (the input list itself is a separate O(n)). Edge cases: arr null or shorter than k (guard up front); duplicates are handled naturally since we compare values.
4. Worked example & why it matters#
Array [23,19,46,32,70,10], k=3. After processing, the min-heap holds the three largest {32,46,70} with root 32 → 3rd largest is 32. When 70 arrives it beats root 23, so 23 leaves; when 10 arrives it loses to root 32, so it's ignored.
Why this matters: the "size-k heap" idea powers every top-K problem — top-K frequent elements, k closest points, k largest in a stream. Recognizing "I only need k of them" is the reusable insight.
II. Connect Ropes with Minimum Cost#
1. Problem#
Heap · GreedyGiven n ropes of different lengths, connect them all into one rope at minimum total cost, where joining two ropes costs the sum of their lengths. Example [5,4,2,8] → cost 36.
Practice: GeeksforGeeks — "Connect n ropes with minimum cost"geeksforgeeks.org/connect-n-ropes-minimum-cost. [+] Equivalent to LeetCode 1167 "Minimum Cost to Connect Sticks" (LeetCode Premium).
2. Insight → optimal#
⭐ TRICK — always merge the two SMALLEST ropes first (Huffman logic). Every time you merge a rope, its length is re-added to future merge costs. So the smallest ropes should be combined earliest to minimize how many times their length gets re-counted. A min-heap hands you the two smallest in O(log n) each.
Algorithm: put all lengths in a min-heap. While size > 1: poll two smallest, sum them, add the sum to a running cost, push the sum back. The sum re-enters as a new "rope." Stop when one rope remains.
⚠ PITFALL — this is "two smallest," not "k smallest." [!] The transcript briefly suggested a max-heap (confusing it with the k-smallest rule). Wrong here: you need the immediate two minimums repeatedly, which is precisely what a min-heap gives. Do not reach for a max-heap.
3. Java (optimal)#
public static int findMinCost(List<Integer> prices) {
PriorityQueue<Integer> pq = new PriorityQueue<>(prices); // min-heap
int cost = 0;
while (pq.size() > 1) {
int x = pq.poll(); // smallest
int y = pq.poll(); // second smallest
int sum = x + y; // cost of this merge
pq.add(sum); // merged rope goes back in
cost += sum; // accumulate
}
return cost;
}Time O(n log n) · Space O(n). Worked: [5,4,2,8] → merge 2+4=6 → merge 5+6=11 → merge 11+8=19 → total 6+11+19 = 36.
4. Why this matters#
This is Huffman coding in disguise. The same "repeatedly combine the two smallest and feed the result back" structure builds Huffman trees and solves file/optimal-merge problems. Recognizing the pattern lets you reuse the min-heap greedy skeleton verbatim.
III. Kth Largest in an Infinite Stream#
1. Problem#
Heap · StreamingIntegers arrive one at a time in an unbounded stream; after each arrival, return the k-th largest element seen so far. A design/stateful variant of Problem I.
Practice: LeetCode 703 — Kth Largest Element in a Streamleetcode.com/problems/kth-largest-element-in-a-stream
2. Insight → optimal#
⭐ TRICK — persist the size-k min-heap as object state. Same engine as Problem I, but the heap lives across calls instead of being rebuilt. It permanently holds the k largest; its root is always the current k-th largest, returned in O(1). Each incoming element costs only O(log k).
add(n) logic: if heap size < k, just add n (still filling). Otherwise, if n > root (the smallest of the top k), poll and add n. If the heap has reached size k, return the root; else return −1 (fewer than k elements exist yet).
⚠ PITFALL — handle the "not enough elements yet" case. Before k values have arrived there is no valid k-th largest; return −1 (or a sentinel) rather than peeking an under-filled heap.
3. Java (optimal)#
class MinHeap {
final PriorityQueue<Integer> pq;
final int k;
public MinHeap(int k) {
this.k = k;
pq = new PriorityQueue<>(k); // min-heap
}
public int add(int n) {
if (pq.size() < k) { // still filling the heap
pq.add(n);
} else if (pq.peek() < n) { // n beats the smallest of top-k
pq.poll();
pq.add(n);
}
if (pq.size() == k) return pq.peek(); // current k-th largest
else return -1; // fewer than k seen
}
}Time O(log k) per add · Space O(k) — you store only k elements regardless of how long the stream runs.
4. Why this matters#
Streaming/online problems reward bounded state. The takeaway — maintain a fixed-size heap as persistent state — transfers to running medians (two heaps), sliding-window top-K, and any "answer a query after each update" design question.
IV. Merge M Sorted Lists#
1. Problem#
Heap · K-way mergeGiven M already-sorted lists of varying length, merge them into one fully sorted sequence, efficiently. Example: 4 lists → single sorted list of all elements.
Practice: LeetCode 23 — Merge k Sorted Listsleetcode.com/problems/merge-k-sorted-lists (linked-list variant; same heap technique).
2. Insight → optimal#
Brute force: concatenate all N elements and sort → O(N log N), throwing away the fact that each list is already sorted.
⭐ TRICK — a size-M min-heap seeded with only the FIRST element of each list. Because lists are pre-sorted, the global minimum must be one of the M list-heads. Poll the min, output it, then push the next element from the same list. The heap never exceeds M, so total cost is O(N log M) — better than O(N log N) since M ≤ N.
⚠ PITFALL — a bare value in the heap isn't enough; store a 3-field node. When you poll the minimum you must know which list it came from and which index within that list, to fetch the successor. Each heap entry needs {value, listNumber, index}. Forgetting the list-id/index is the classic bug here.
3. Java (optimal)#
class Node implements Comparable<Node> {
int value; // the element
int i; // which list it came from
int index; // its position within that list
Node(int value, int i, int index) { this.value = value; this.i = i; this.index = index; }
public int compareTo(Node o) { return this.value - o.value; } // min-heap by value
}
public static void printSorted(List<List<Integer>> lists) {
PriorityQueue<Node> pq = new PriorityQueue<>();
// seed: first element of every non-empty list
for (int i = 0; i < lists.size(); i++) if (lists.get(i).size() >= 1) pq.add(new Node(lists.get(i).get(0), i, 0));
while (!pq.isEmpty()) {
Node min = pq.poll();
System.out.print(min.value + " "); // emit the smallest
int nextIdx = min.index + 1; // successor in the SAME list
if (nextIdx < lists.get(min.i).size()) {
min.index = nextIdx;
min.value = lists.get(min.i).get(nextIdx);
pq.add(min); // push successor back
}
}
}Time O(N log M) · Space O(M), where N = total elements, M = number of lists. The heap holds at most one live entry per list.
4. Why heap beats sorting#
💡 TIP — "already sorted" is a signal to exploit, not re-sort. Dumping everything into one array and sorting ignores the given structure. The heap's root-is-minimum property lets you produce output in order with only M candidates in play at any time — the reusable idea behind external merge sort and merging sorted files too large for memory.
V. Is a Binary Tree a Min-Heap?#
1. Problem#
Tree · DFS/RecursionGiven a binary tree, decide whether it is a min-heap — i.e. (a) a complete binary tree, and (b) every node's value ≤ both of its children's values.
Practice: GeeksforGeeks — "Check if a given binary tree is heap"geeksforgeeks.org/check-if-a-given-binary-tree-is-heap. [+] Not a standard LeetCode problem.
2. Insight → optimal#
⭐ TRICK — check completeness and ordering in ONE DFS using array-indexing. Imagine the tree stored in an array: the root is index 0, and a node at index i has children at 2i+1 and 2i+2. Recurse with three parameters — (node, i, n) where n = total node count. If any node's index i ≥ n, the tree has a "gap" → not complete → not a heap. This folds the completeness check into the same traversal as the value check.
⚠ PITFALL — null-check children before comparing values. Comparing node.data against a null child throws a NullPointerException. Guard each child with != null first.
💡 TIP — know what your recursive booleans mean. left = "is the left subtree of the current node a valid min-heap?"; same for right. The return is left && right after the current node passes its own checks. Being able to state the meaning of each recursive result is what lets you adapt this to any new tree problem on the spot.
3. Java (optimal)#
// count total nodes
private static int size(Node root) {
if (root == null) return 0;
return 1 + size(root.left) + size(root.right);
}
private static boolean isHeap(Node root, int i, int n) {
if (root == null) return true; // empty subtree is a valid heap
if (i >= n) return false; // index out of range → not complete
// min-heap violation: a child smaller than the parent
if ((root.left != null && root.left.data <= root.data) ||(root.right != null && root.right.data <= root.data)) return false;
boolean left = isHeap(root.left, 2*i + 1, n);
boolean right = isHeap(root.right, 2*i + 2, n);
return left && right;
}
public static boolean isHeap(Node root) {
return isHeap(root, 0, size(root));
}Time O(n) · Space O(h) where h = tree height (recursion stack). [!] One definition writes the violation as child <= parent (strict min-heap disallowing equal children); another describes parent <= child (allowing equals). Use whichever definition the problem gives — the code above follows the strict form. Note: order the two recursive calls left-then-right (2i+1 before 2i+2) as shown.
4. Why this matters#
The array-index-during-DFS trick generalizes. Encoding a tree's positional structure as indices lets you verify completeness, serialize/deserialize, or map a tree to a flat array in one pass — reusable for segment trees, binary-heap arrays, and complete-tree validation problems.
VI. Patterns Recap#
Every problem, mapped#
| # | Problem | Technique | Key insight | Time / Space |
|---|---|---|---|---|
| I | Kth largest in array | Size-k min-heap | Root of a min-heap of the k largest = k-th largest | O(n log k) / O(k) |
| II | Connect ropes min cost | Min-heap + greedy (Huffman) | Merge the two smallest first, feed the sum back | O(n log n) / O(n) |
| III | Kth largest in stream | Persistent size-k min-heap | Keep the top-k heap as state; root answers in O(1) | O(log k) add / O(k) |
| IV | Merge M sorted lists | Size-M min-heap (k-way merge) | Heap of list heads; node carries {value, list, index} | O(N log M) / O(M) |
| V | Is tree a min-heap | DFS + array indexing | Child index i≥n ⇒ incomplete; check value + completeness together | O(n) / O(h) |
The meta-pattern: reach for a heap whenever you repeatedly need the current min or max of a changing set — top-K, greedy-merge, streaming queries, and k-way merges all reduce to "root gives me the extreme in O(1), maintenance is O(log size)."
VII. Notebook Summary (hand-copy / flashcards)#
Foundations#
HEAP = complete binary tree + ordering rule
min-heap: parent ≤ children → MIN at root
max-heap: parent ≥ children → MAX at root
Java PriorityQueue = min-heap; reverseOrder() = max-heap
add / poll = O(log n) ; peek = O(1)
idx (0-based): left=2i+1, right=2i+2
heapify walks the height → O(log n)
⭐ k LARGEST → size-k MIN-heap ; k SMALLEST → size-k MAX-heap
doodle: tree with root circled = "min lives here ↑"I. Kth Largest (array)#
tag: heap / top-K
insight: root of size-k MIN-heap = k-th largest
T/S: O(n log k) / O(k)
skeleton:
pq = min-heap(first k)
for rest: if x > pq.peek(): poll(); add(x)
return pq.peek()
⭐ use MIN-heap not max-heap (max = O(n log n))
practice: LeetCode 215
doodle: [ min-heap size k ] ← only the k biggest surviveII. Connect Ropes#
tag: heap / greedy (Huffman)
insight: merge two SMALLEST first, push sum back
T/S: O(n log n) / O(n)
skeleton:
pq = min-heap(all)
while size>1: a=poll; b=poll; cost+=a+b; add(a+b)
return cost
⭐ two smallest ; ⚠ NOT a max-heap
practice: GfG "Connect n ropes"
doodle: min-heap → pull 2 lowest → sum ↺ back inIII. Kth Largest (stream)#
tag: heap / streaming
insight: persist size-k MIN-heap as state; root = answer
T/S: O(log k) per add / O(k)
skeleton:
add(n): if size<k: add(n)
elif n>peek(): poll(); add(n)
return size==k ? peek() : -1
⚠ return -1 until k elements seen
practice: LeetCode 703
doodle: stream → [k-slot min-heap] → peek()IV. Merge M Sorted Lists#
tag: heap / k-way merge
insight: size-M min-heap of list HEADS; node={val,list,idx}
T/S: O(N log M) / O(M)
skeleton:
push first elem of each list (val,i,0)
while pq: m=poll; emit m.val
if m.idx+1 < list[m.i].size: push next elem
⚠ store list# + index, not just value
practice: LeetCode 23
doodle: M lists → heads → [heap M] → pop min ↺ nextV. Tree is Min-Heap?#
tag: tree / DFS + array-index
insight: node i → children 2i+1, 2i+2 ; i≥n ⇒ gap ⇒ incomplete
T/S: O(n) / O(h)
skeleton:
isHeap(node,i,n):
if node==null: return true
if i>=n: return false // not complete
if child < parent: return false
return isHeap(L,2i+1,n) && isHeap(R,2i+2,n)
⚠ null-check children first (NPE)
practice: GfG "binary tree is heap"
doodle: tree over array 0..n, gap = false