← Back to All Patterns

Two Heaps Pattern

⚖️ Two Heaps - Finding Median from Stream

Numbers Added So Far:

5

Max Heap (Smaller Half)

5
← Largest of small

Min Heap (Larger Half)

Empty

Current Median:

5

Step 1: Add 5 to max heap, median = 5

Progress: 1 / 5

Speed:

Key Concept:

Max heap stores smaller half (largest on top), Min heap stores larger half (smallest on top). Median is between the two tops!

🎯 Explain Like I'm 5...

Imagine you have numbers written on cards and want to find the MIDDLE card super fast! Use TWO special boxes! 📦📦

📦 Two Magic Boxes:

  • Left Box (Small Numbers): Keeps the SMALLER half, but BIGGEST one on top! 📦⬆️
  • Right Box (Big Numbers): Keeps the BIGGER half, but SMALLEST one on top! 📦⬇️

🎲 Example with cards: 1, 2, 3, 5, 6, 7

  • Left Box has: 1, 2, 3 (with 3 on top) 👆
  • Right Box has: 5, 6, 7 (with 5 on top) 👆
  • Middle number? Look at the tops: between 3 and 5! = 4

🚀 Why two boxes? Instead of sorting ALL numbers every time, just peek at the tops of both boxes! The middle is always between those two tops. Super fast! It's like having two teams and their captains meet in the middle! ⚖️

When Should You Use This Pattern?

  • Finding the median of a number stream
  • Problems with two halves of data
  • Need to find the middle element(s) efficiently
  • Balancing elements to maintain order statistics

📝 Example 1: Find Median from Data Stream

Design a data structure that supports adding numbers and finding the median efficiently.

Step-by-Step Thinking:

  1. Use max heap for smaller half, min heap for larger half
  2. Keep heaps balanced (sizes differ by at most 1)
  3. Max heap's top is largest of small numbers
  4. Min heap's top is smallest of large numbers
  5. Median is between these two tops!
MedianFinder.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.util.*;
public class MedianFinder {
// Max heap for smaller half (largest element on top)
PriorityQueue<Integer> maxHeap;
// Min heap for larger half (smallest element on top)
PriorityQueue<Integer> minHeap;
public MedianFinder() {
maxHeap = new PriorityQueue<>((a, b) -> b - a); // Max heap
minHeap = new PriorityQueue<>(); // Min heap (default)
}
public void addNum(int num) {
// Add to max heap first
if (maxHeap.isEmpty() || num <= maxHeap.peek()) {
maxHeap.offer(num);
} else {
minHeap.offer(num);
}
// Balance the heaps (sizes should differ by at most 1)
if (maxHeap.size() > minHeap.size() + 1) {
minHeap.offer(maxHeap.poll());
} else if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
}
public double findMedian() {
if (maxHeap.size() == minHeap.size()) {
// Even number of elements - average of two middle elements
return (maxHeap.peek() + minHeap.peek()) / 2.0;
} else {
// Odd number - max heap has one more element
return maxHeap.peek();
}
}
public static void main(String[] args) {
MedianFinder mf = new MedianFinder();
mf.addNum(1);
System.out.println("Median: " + mf.findMedian()); // 1.0
mf.addNum(2);
System.out.println("Median: " + mf.findMedian()); // 1.5
mf.addNum(3);
System.out.println("Median: " + mf.findMedian()); // 2.0
mf.addNum(4);
System.out.println("Median: " + mf.findMedian()); // 2.5
}
}

📝 Example 2: Sliding Window Median

Find the median of each window of size k sliding through an array.

SlidingWindowMedian.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import java.util.*;
public class SlidingWindowMedian {
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
public double[] medianSlidingWindow(int[] nums, int k) {
double[] result = new double[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// Add new element
addNum(nums[i]);
// Remove element going out of window
if (i >= k) {
removeNum(nums[i - k]);
}
// Calculate median when window is full
if (i >= k - 1) {
result[i - k + 1] = findMedian();
}
}
return result;
}
private void addNum(int num) {
if (maxHeap.isEmpty() || num <= maxHeap.peek()) {
maxHeap.offer(num);
} else {
minHeap.offer(num);
}
rebalance();
}
private void removeNum(int num) {
if (num <= maxHeap.peek()) {
maxHeap.remove(num);
} else {
minHeap.remove(num);
}
rebalance();
}
private void rebalance() {
if (maxHeap.size() > minHeap.size() + 1) {
minHeap.offer(maxHeap.poll());
} else if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
}
private double findMedian() {
if (maxHeap.size() == minHeap.size()) {
return (maxHeap.peek() + minHeap.peek()) / 2.0;
}
return maxHeap.peek();
}
public static void main(String[] args) {
SlidingWindowMedian swm = new SlidingWindowMedian();
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
double[] medians = swm.medianSlidingWindow(nums, k);
System.out.println("Medians: " + Arrays.toString(medians));
// Output: [1.0, -1.0, -1.0, 3.0, 5.0, 6.0]
}
}

📝 Example 3: IPO (Maximize Capital)

Given projects with capital requirements and profits, maximize capital by selecting k projects.

IPO.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.*;
public class IPO {
public static int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {
int n = profits.length;
// Min heap for projects by capital requirement
PriorityQueue<int[]> minCapitalHeap = new PriorityQueue<>((a, b) -> a[0] - b[0]);
// Max heap for profits of affordable projects
PriorityQueue<int[]> maxProfitHeap = new PriorityQueue<>((a, b) -> b[1] - a[1]);
// Add all projects to min heap
for (int i = 0; i < n; i++) {
minCapitalHeap.offer(new int[]{capital[i], profits[i]});
}
// Select k projects
for (int i = 0; i < k; i++) {
// Move all affordable projects to max profit heap
while (!minCapitalHeap.isEmpty() && minCapitalHeap.peek()[0] <= w) {
maxProfitHeap.offer(minCapitalHeap.poll());
}
// If no affordable projects, we're done
if (maxProfitHeap.isEmpty()) {
break;
}
// Pick the most profitable project
w += maxProfitHeap.poll()[1];
}
return w;
}
public static void main(String[] args) {
int k = 2; // Can select 2 projects
int w = 0; // Initial capital
int[] profits = {1, 2, 3};
int[] capital = {0, 1, 1};
int maxCapital = findMaximizedCapital(k, w, profits, capital);
System.out.println("Maximum capital: " + maxCapital);
// Output: 4
// Pick project 0 (profit=1, capital=0), then project 2 (profit=3, capital=1)
}
}

🔑 Key Points to Remember

  • 1️⃣Max heap stores smaller half, Min heap stores larger half
  • 2️⃣Keep heaps balanced - sizes should differ by at most 1
  • 3️⃣Time Complexity: O(log n) for insert, O(1) for median
  • 4️⃣Space Complexity: O(n) for storing all elements

💪 Practice Problems

  • Find Right Interval
  • Next Interval
  • Minimize Deviation in Array
  • Schedule Tasks on Minimum Machines