Numbers arrive one at a time and you must report the running median on demand. Split the data across two heaps — a max-heap for the smaller half and a min-heap for the larger half — kept balanced so the median always sits right on their tops.
Design a structure with two operations: addNum(x) feeds a number from an infinite stream, and findMedian() returns the median of everything seen so far. The median is the middle value of the sorted data, or the average of the two middle values when the count is even. Stream 5, 2, 8, 1, 9, 4 → after all six, sorted is [1,2,4,5,8,9] so the median is (4 + 5) / 2 = 4.5.
low) and the larger half in a min-heap (high). If their sizes differ by at most one, the median is either the average of the two tops (sizes equal) or the top of the bigger heap. Both tops are O(1) to read.low is empty or x ≤ low.max, push x to low; otherwise push it to high. This keeps every number in low ≤ every number in high.|low| > |high| + 1, move low's max into high. If |high| > |low|, move high's min into low. Now the sizes differ by at most one (with low holding the extra).(low.max + high.min) / 2. Otherwise low is the bigger heap ⇒ median = low.max.low is always the one that grows by one on odd counts, so for an odd total the median is unambiguously low.max — never high.min. Pick a convention and keep the rebalance asymmetric to match it.A sorted array gives O(1) median but each addNum is O(n) for the shift. The two-heap structure trades that for O(log n) inserts while still reading the median in O(1). Space is O(n) to hold the stream. If values are bounded small integers, a Fenwick/BIT or bucket-count approach can also hit O(log n) or O(1) per query.
low holds the smaller half (max on top), high holds the larger half (min on top).1class MedianFinder {2 // low = max-heap (smaller half), high = min-heap (larger half).3 // We keep |low| === |high|, or |low| === |high| + 1.4▶ private low: MaxHeap = new MaxHeap();5▶ private high: MinHeap = new MinHeap();67 addNum(num: number): void {8 // 1. Route by value so low always holds the smaller numbers.9 if (this.low.size === 0 || num <= this.low.peek()) this.low.push(num);10 else this.high.push(num);1112 // 2. Rebalance so the two sizes differ by at most one.13 if (this.low.size > this.high.size + 1) {14 this.high.push(this.low.pop());15 } else if (this.high.size > this.low.size) {16 this.low.push(this.high.pop());17 }18 }1920 findMedian(): number {21 if (this.low.size === this.high.size) {22 // Even count: average the two middle elements (the heap tops).23 return (this.low.peek() + this.high.peek()) / 2;24 }25 // Odd count: low carries the extra element, so its top IS the median.26 return this.low.peek();27 }28}
class MedianFinder {
// low = max-heap (smaller half), high = min-heap (larger half).
// We keep |low| === |high|, or |low| === |high| + 1.
private low: MaxHeap = new MaxHeap();
private high: MinHeap = new MinHeap();
addNum(num: number): void {
// 1. Route by value so low always holds the smaller numbers.
if (this.low.size === 0 || num <= this.low.peek()) this.low.push(num);
else this.high.push(num);
// 2. Rebalance so the two sizes differ by at most one.
if (this.low.size > this.high.size + 1) {
this.high.push(this.low.pop());
} else if (this.high.size > this.low.size) {
this.low.push(this.high.pop());
}
}
findMedian(): number {
if (this.low.size === this.high.size) {
// Even count: average the two middle elements (the heap tops).
return (this.low.peek() + this.high.peek()) / 2;
}
// Odd count: low carries the extra element, so its top IS the median.
return this.low.peek();
}
}low is a max-heap holding the smaller half, so its top is the largest of the small numbers. high is a min-heap holding the larger half, so its top is the smallest of the big numbers. The invariant is |low| === |high| or |low| === |high| + 1.low.peek()) belongs to the smaller half, so it goes into low; everything else goes into high. The empty-low case sends the very first number to low.low or the min of high) restores the size invariant. The asymmetric checks (> high.size + 1 vs > low.size) ensure low is the heap that carries the extra element on odd counts.low holds the extra, the single middle element is low.peek().addNum is O(log n) (one or two heap operations); findMedian is O(1) (read one or two tops). Space is O(n) to store the stream.class MedianFinder {
// low = max-heap (smaller half), high = min-heap (larger half).
// We keep |low| === |high|, or |low| === |high| + 1.
private low: MaxHeap = new MaxHeap();
private high: MinHeap = new MinHeap();
addNum(num: number): void {
// 1. Route by value so low always holds the smaller numbers.
if (this.low.size === 0 || num <= this.low.peek()) this.low.push(num);
else this.high.push(num);
// 2. Rebalance so the two sizes differ by at most one.
if (this.low.size > this.high.size + 1) {
this.high.push(this.low.pop());
} else if (this.high.size > this.low.size) {
this.low.push(this.high.pop());
}
}
findMedian(): number {
if (this.low.size === this.high.size) {
// Even count: average the two middle elements (the heap tops).
return (this.low.peek() + this.high.peek()) / 2;
}
// Odd count: low carries the extra element, so its top IS the median.
return this.low.peek();
}
}addNum is O(log n) (one or two heap operations); findMedian is O(1) (read one or two tops). Space is O(n) to store the stream.Keep one sorted array; binary-search the insert slot so reading the median is a trivial O(1) index lookup.
class MedianFinder {
// Numbers kept in ascending sorted order at all times.
private nums: number[] = [];
addNum(num: number): void {
// Binary-search the leftmost index where num can be inserted
// while keeping nums sorted (lower_bound).
let lo = 0;
let hi = this.nums.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (this.nums[mid] < num) lo = mid + 1;
else hi = mid;
}
// splice shifts the tail right by one — O(n).
this.nums.splice(lo, 0, num);
}
findMedian(): number {
const n = this.nums.length;
const mid = n >> 1;
if (n % 2 === 1) {
// Odd count: the single middle element.
return this.nums[mid];
}
// Even count: average the two middle elements.
return (this.nums[mid - 1] + this.nums[mid]) / 2;
}
}When values are confined to a small range 0..R-1, tally counts and walk the buckets to the middle — no comparisons, no heaps.
class MedianFinder {
// Follow-up assumption: every value is an integer in [0, R-1].
// Here R = 101 to cover the classic 0..100 constraint.
private static readonly R = 101;
private count: number[] = new Array<number>(MedianFinder.R).fill(0);
private total = 0;
addNum(num: number): void {
// O(1): just bump the bucket for this value.
this.count[num]++;
this.total++;
}
findMedian(): number {
// For an odd total we want the element at index mid (0-based).
// For an even total we want the average of indices mid-1 and mid.
const odd = this.total % 2 === 1;
const leftRank = (this.total - 1) >> 1; // index of the lower middle
const rightRank = this.total >> 1; // index of the upper middle
let seen = 0;
let left = -1;
let right = -1;
for (let v = 0; v < MedianFinder.R; v++) {
const c = this.count[v];
if (c === 0) continue;
// Does this bucket cover the lower-middle index?
if (left === -1 && seen + c > leftRank) left = v;
// Does this bucket cover the upper-middle index?
if (right === -1 && seen + c > rightRank) {
right = v;
break; // right is the later index, so we are done once it is found
}
seen += c;
}
return odd ? right : (left + right) / 2;
}
}findMedian to O(log R).| running / streaming median | two heaps (max-heap + min-heap) |
| need the middle without full sort | balanced partition at the boundary |
| sizes off by 2 after insert | move one element across to rebalance |
| sliding-window median | two heaps + lazy deletion |
// low = max-heap (smaller half), high = min-heap (larger half)
addNum(num) {
if (low.size === 0 || num <= low.peek()) low.push(num);
else high.push(num);
if (low.size > high.size + 1) high.push(low.pop());
else if (high.size > low.size) low.push(high.pop());
}
findMedian() {
if (low.size === high.size) return (low.peek() + high.peek()) / 2;
return low.peek();
}low = max-heap for the smaller half (top is its largest); high = min-heap for the larger half (top is its smallest).