Heap Fundamentals
A heap is a complete binary tree satisfying the heap property:
- Min-heap: Parent ≤ children (smallest at root)
- Max-heap: Parent ≥ children (largest at root)
Node Definition (Array-Based)
// No explicit node class needed - use array
// For node at index i:
// Left child: 2*i + 1
// Right child: 2*i + 2
// Parent: (i-1) / 2
Visual Example (Min-Heap)
Array: [1, 3, 5, 7, 9, 8, 6]
Tree:
1
/ \
3 5
/ \\ / \
7 9 8 6
Index: 0 1 2 3 4 5 6
Java PriorityQueue
// Min-heap (default)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
// Max-heap
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
// Min-heap with custom comparator
PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> a[0] - b[0]);
// Operations
minHeap.offer(5); // Insert - O(log n)
int min = minHeap.poll(); // Extract min - O(log n)
int peek = minHeap.peek(); // View min - O(1)
int size = minHeap.size(); // Size - O(1)
Manual Heap Implementation
class MinHeap {
private int[] heap;
private int size;
private int capacity;
public MinHeap(int capacity) {
this.capacity = capacity;
this.size = 0;
heap = new int[capacity];
}
public void insert(int val) {
if (size == capacity) throw new RuntimeException("Heap full");
heap[size] = val;
size++;
siftUp(size - 1);
}
public int extractMin() {
if (size == 0) throw new RuntimeException("Heap empty");
int min = heap[0];
heap[0] = heap[size - 1];
size--;
siftDown(0);
return min;
}
private void siftUp(int i) {
while (i > 0) {
int parent = (i - 1) / 2;
if (heap[i] < heap[parent]) {
swap(i, parent);
i = parent;
} else break;
}
}
private void siftDown(int i) {
while (true) {
int smallest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < size && heap[left] < heap[smallest]) smallest = left;
if (right < size && heap[right] < heap[smallest]) smallest = right;
if (smallest != i) {
swap(i, smallest);
i = smallest;
} else break;
}
}
private void swap(int i, int j) {
int temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
}
Heapify (Build Heap from Array) - O(n)
public void heapify(int[] arr) {
int n = arr.length;
// Start from last non-leaf node
for (int i = n / 2 - 1; i >= 0; i--) {
siftDown(arr, n, i);
}
}
private void siftDown(int[] arr, int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[largest]) largest = left;
if (right < n && arr[right] > arr[largest]) largest = right;
if (largest != i) {
swap(arr, i, largest);
siftDown(arr, n, largest);
}
}
Complexity
| Operation | Time |
|---|---|
| Insert | O(log n) |
| Extract min/max | O(log n) |
| Peek | O(1) |
| Heapify | O(n) |
| Search | O(n) |
Space: O(n)
Heap Applications
Top K Elements
Find K largest elements using min-heap of size K:
public int[] topK(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll(); // Remove smallest
}
}
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = minHeap.poll();
}
return result;
}
Kth Largest Element
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll();
}
}
return minHeap.peek();
}
Median Finder (Two Heaps)
class MedianFinder {
PriorityQueue<Integer> maxHeap; // Lower half
PriorityQueue<Integer> minHeap; // Upper half
public MedianFinder() {
maxHeap = new PriorityQueue<>(Collections.reverseOrder());
minHeap = new PriorityQueue<>();
}
public void addNum(int num) {
maxHeap.offer(num);
minHeap.offer(maxHeap.poll()); // Balance
if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
}
public double findMedian() {
if (maxHeap.size() > minHeap.size()) {
return maxHeap.peek();
}
return (maxHeap.peek() + minHeap.peek()) / 2.0;
}
}
Merge K Sorted Lists
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
for (ListNode list : lists) {
if (list != null) pq.offer(list);
}
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (!pq.isEmpty()) {
curr.next = pq.poll();
curr = curr.next;
if (curr.next != null) pq.offer(curr.next);
}
return dummy.next;
}
Task Scheduler
public int leastInterval(char[] tasks, int n) {
int[] count = new int[26];
for (char c : tasks) count[c - 'A']++;
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
for (int c : count) {
if (c > 0) maxHeap.offer(c);
}
int intervals = 0;
while (!maxHeap.isEmpty()) {
int cycle = 0;
List<Integer> temp = new ArrayList<>();
for (int i = 0; i <= n; i++) {
if (!maxHeap.isEmpty()) {
int val = maxHeap.poll() - 1;
if (val > 0) temp.add(val);
cycle++;
}
}
for (int val : temp) maxHeap.offer(val);
intervals += maxHeap.isEmpty() ? cycle : n + 1;
}
return intervals;
}
When to Use Heap
- Top K / Kth element: min-heap of size K
- Merge sorted: K-way merge with min-heap
- Median maintenance: two heaps (max + min)
- Scheduling/priorities: process by priority
- Sliding window max/min: heap with lazy deletion
Practice Problems
Given an integer array nums and an integer k, return the kth largest element in the array. Note: it is the kth largest element in sorted order, not the kth distinct element.
Example:
Input: nums = [3,2,1,5,6,4], k = 2
Output: 5
The 2nd largest element is 5 (sorted: [1,2,3,4,5,6]).
Edge Cases:
- k equals array length — answer is the minimum element
- k equals 1 — answer is the maximum element
- All elements same value
- Negative numbers present
- Single element array
Design a data structure that supports the following two operations: addNum(int num) adds the integer num from the data stream, and findMedian() returns the median of all elements so far.
Example:
Input: addNum(1), addNum(2), findMedian(), addNum(3), findMedian()
Output: [null, null, 1.5, null, 2.0]
Median of [1,2] is 1.5. Median of [1,2,3] is 2.
Edge Cases:
- Single element: median is that element
- Two elements: median is average
- Odd number of elements: median is the middle element
Given a characters array tasks where each task must be done at least once. Each task can be done in one unit of time. There is a non-negative integer n that represents the cooldown period between two same tasks. Return the least number of intervals the CPU will take to finish all tasks.
Example:
Input: tasks = ['A','A','A','B','B','B'], n = 2
Output: 8
A -> B -> idle -> A -> B -> idle -> A -> B
Edge Cases:
- n = 0 — just execute all tasks in any order, answer is tasks.length
- All tasks same type — must insert n idle slots between each
- More unique task types than n — no idle slots needed
- Single task type, n large
- All unique tasks
Design a class to find the kth largest element in a stream.
Example:
Input: KthLargest(3, [4,5,8,2]).add(3) → 4
Output: 4
3rd largest in [2,3,4,5,8] is 4.
Edge Cases:
- k equals nums.length: heap contains all elements
- k = 1: heap tracks the maximum
- Empty initial stream: heap builds as adds come in
Quiz
1. What is the time complexity of building a heap from an unsorted array?
2. To find the Kth largest element, which heap should you use?
3. What is a common mistake when implementing Heap (Priority Queue)?
Flashcards
Question
What is the difference between min-heap and max-heap?
Click to reveal answer
Answer
Min-heap: parent ≤ children, root is minimum. Max-heap: parent ≥ children, root is maximum. Use min-heap for Kth largest (keep K largest), max-heap for Kth smallest.
Question
What is the time complexity of heapify (build heap from array)?
Click to reveal answer
Answer
O(n) - not O(n log n). Most nodes are leaves and require no sifting. The sum of sift-down heights converges to O(n).
Question
Heap (Priority Queue) best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1. Heap is a complete binary tree stored as an array
- 2. Min-heap for Kth largest, max-heap for Kth smallest
- 3. Heapify is O(n), not O(n log n)
- 4. Two heaps (max + min) solve median maintenance problems
Interview Tips
- • Know Java PriorityQueue API: offer, poll, peek, size
- • For top-K, use heap of size K (not sorting all elements)
- • Mention that heap is not sorted - only root is guaranteed
- • For median, explain the two-heap invariant clearly
Cheat Sheet
Heap Cheat Sheet
Heap Property:
- Min-heap: parent ≤ children (root = min)
- Max-heap: parent ≥ children (root = max)
Array Navigation:
- Left child of i: 2*i + 1
- Right child of i: 2*i + 2
- Parent of i: (i-1) / 2
Java PriorityQueue:
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
minHeap.offer(val); // Insert O(log n)
int min = minHeap.poll(); // Extract min O(log n)
int peek = minHeap.peek(); // View min O(1)
Operations:
| Operation | Time |
|---|---|
| Insert | O(log n) |
| Extract | O(log n) |
| Peek | O(1) |
| Heapify | O(n) |
Common Patterns:
- Kth largest → min-heap of size K
- Kth smallest → max-heap of size K
- Median → two heaps (max + min)
- Merge K sorted → min-heap with K lists
- Sliding window max → heap with lazy deletion
Key Insight: Heap gives O(log n) insert + O(1) peek + O(log n) extract. Use when you need repeated min/max access.