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
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.
Optimal Solution — O(log k) per add time, O(k) space
Min-heap of size K
class KthLargest {
PriorityQueue<Integer> minHeap;
int k;
public KthLargest(int k, int[] nums) {
this.k = k;
minHeap = new PriorityQueue<>();
for (int num : nums) add(num);
}
public int add(int val) {
minHeap.offer(val);
if (minHeap.size() > k) minHeap.poll();
return minHeap.peek();
}
}Edge Cases:
- k equals array length
- Adding smallest element
- Adding largest element
Design a data structure that supports addNum and findMedian. Median is the middle value.
Example:
Input: addNum(1), addNum(2), findMedian() → 1.5, addNum(3), findMedian() → 2
Output: [null, null, 1.5, null, 2]
Median of [1,2] is 1.5, median of [1,2,3] is 2.
Optimal Solution — O(log n) add, O(1) findMedian time, O(n) space
Two heaps: max-heap for lower half, min-heap for upper half
class MedianFinder {
PriorityQueue<Integer> maxHeap;
PriorityQueue<Integer> minHeap;
public MedianFinder() {
maxHeap = new PriorityQueue<>(Collections.reverseOrder());
minHeap = new PriorityQueue<>();
}
public void addNum(int num) {
maxHeap.offer(num);
minHeap.offer(maxHeap.poll());
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;
}
}Edge Cases:
- Single element
- Even number of elements
- All same values
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
Optimal Solution — O(n) time, O(1) space
Greedy: schedule most frequent tasks first, use cooldown
class Solution {
public int leastInterval(char[] tasks, int n) {
int[] count = new int[26];
for (char c : tasks) count[c - 'A']++;
Arrays.sort(count);
int maxFreq = count[25];
int idleSlots = (maxFreq - 1) * n;
for (int i = 24; i >= 0; i--) {
idleSlots -= Math.min(count[i], maxFreq - 1);
}
return Math.max(tasks.length, tasks.length + Math.max(0, idleSlots));
}
}Edge Cases:
- n = 0: return tasks.length
- Single task type
- All unique tasks
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 the primary purpose of Heap (Priority Queue)?
4. 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
What is Heap (Priority Queue)?
Click to reveal answer
Answer
Heap (Priority Queue) is a key concept in software engineering.
Question
When to use Heap (Priority Queue)?
Click to reveal answer
Answer
Use Heap (Priority Queue) when building production systems that require reliability, scalability, and maintainability.
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.