← PatternsHeap → Two Heaps (Running Median)⚖️ Two Heaps
⚖️
Two Heaps — Running Median
Keep a LO max-heap (lower half) and HI min-heap (upper half), always balanced within 1. After each insert, lo.peek() and hi.peek() give the median in O(1).
Running median — median changes as new numbers arrive
Two halves invariant — lo holds ≤ half, hi holds > half; |lo.size - hi.size| ≤ 1
Balance rule — always add to lo first, then move lo.top→hi; if lo smaller, move hi.top→lo
O(1) query — median = lo.peek() (odd total) or avg of both tops (even)
💡 Key insight: LO always has ≥ HI in size, so lo.peek() is always the median (or one of the two middle elements). Never query the full heaps — just the tops!
⚡ Complexity
addNum
O(log n)
findMedian
O(1)
Each insertion does at most 2 heap operations (offer+poll) → O(log n).
Total space: O(n) — each element is in exactly one heap.
Compare: sorting after each insert = O(n²), order-statistics tree = O(log n) but complex.
Two heaps: simple, fast, standard interview approach.
Live Animation · Two Heaps (Running Median) · LC #295
Problem
Design a data structure supporting addNum and findMedian. Insert stream = [5,15,1,3,8,7,9,25,10,6] one by one and report the running median after each insertion. Use a LO max-heap (lower half) + HI min-heap (upper half), kept balanced within 1 element.
Input: stream = [5,15,1,3,8,7,9,25,10,6] · Answer: final median = 7.5 (sorted: [1,3,5,6,7,8,9,10,15,25])
Step 0 / 11
⚖️ Init➕ Add → LO→ Move to HI← Rebalance📊 Median
📡 Input Stream
LO size0
HI size0
Median—
🔻
LO — Max-Heap
Lower half · max at top
0
empty
Balance
→ HI
📊 Median
—
← LO
🔺
HI — Min-Heap
Upper half · min at top
0
empty
⚖️ Two Heaps: LO (max-heap) holds lower half, HI (min-heap) holds upper half. Stream numbers in, get the median any time in O(1)!
PriorityQueue<Integer> lo = new PriorityQueue<>(Collections.reverseOrder());
🧩 Pattern Skeleton
Java 8
// Two Heaps — Running Median ─────────────────────────────────PriorityQueue<Integer> lo = newPriorityQueue<>(Collections.reverseOrder()); // max-heapPriorityQueue<Integer> hi = newPriorityQueue<>(); // min-heapvoidaddNum(int num) {
lo.offer(num); // step 1: always add to lo
hi.offer(lo.poll()); // step 2: move lo's max to hiif (lo.size() < hi.size()) // step 3: rebalance if needed
lo.offer(hi.poll());
}
doublefindMedian() {
return lo.size() > hi.size()
? lo.peek() // odd total: lo has the middle
: (lo.peek() + hi.peek()) / 2.0; // even: average of two middles
}