Find Median from Data Stream
Problem (restated)
Support addNum(num) and findMedian() over a growing stream. Median is middle (odd) or average of two middles (even).
Intuition
Keep lower half in a max-heap, upper half in a min-heap. Balance sizes so lo has n/2 or n/2+1 elements.
Approaches
Two heaps (max + min)
Tested onlyIdea. Push into lo or hi by value; rebalance so lo.Count ≥ hi.Count and |lo-hi| ≤ 1. Median from peeks.
Walkthrough. add 1,2,3 → lo=[2,1] hi=[3] → median 2; add 4 → average of 2 and 3 = 2.5.
Trade-offs. Sorted list is O(n) insert; dual heap is the interview target.
export class MedianFinder {
private lo: number[] = []; // max-heap (negated via sort desc)
private hi: number[] = []; // min-heap
addNum(num: number): void {
if (this.lo.length === 0 || num <= this.lo[0]!) {
this.lo.push(num);
this.lo.sort((a, b) => b - a);
} else {
this.hi.push(num);
this.hi.sort((a, b) => a - b);
}
if (this.lo.length > this.hi.length + 1) {
this.hi.push(this.lo.shift()!);
this.hi.sort((a, b) => a - b);
} else if (this.hi.length > this.lo.length) {
this.lo.push(this.hi.shift()!);
this.lo.sort((a, b) => b - a);
}
}
findMedian(): number {
if (this.lo.length > this.hi.length) return this.lo[0]!;
return (this.lo[0]! + this.hi[0]!) / 2;
}
}
export class MedianFinder {
private lo: number[] = []; // max-heap (negated via sort desc)
private hi: number[] = []; // min-heap
addNum(num: number): void {
if (this.lo.length === 0 || num <= this.lo[0]!) {
this.lo.push(num);
this.lo.sort((a, b) => b - a);
} else {
this.hi.push(num);
this.hi.sort((a, b) => a - b);
}
if (this.lo.length > this.hi.length + 1) {
this.hi.push(this.lo.shift()!);
this.hi.sort((a, b) => a - b);
} else if (this.hi.length > this.lo.length) {
this.lo.push(this.hi.shift()!);
this.lo.sort((a, b) => b - a);
}
}
findMedian(): number {
if (this.lo.length > this.hi.length) return this.lo[0]!;
return (this.lo[0]! + this.hi[0]!) / 2;
}
}
Template connection
Heap top-k / order statistics with two priority queues.
Deep dive
Two heaps keep the lower half (max-heap) and upper half (min-heap) balanced by size. Invariant: every value in lower ≤ every value in upper, and sizes differ by at most 1. Median is the max of lower (odd count) or average of the two roots (even). Rebalance after each insert. This is online: you never re-sort the whole stream.
Sorted list baseline
Tested onlyIdea. Keep numbers sorted; binary-search insert; median at middle.
Trade-offs. Simple and correct; two-heap version is O(log n) insert for interviews.
/** Naive median: keep a sorted array (O(n) insert). */
export class MedianFinderSorted {
private a: number[] = [];
addNum(num: number): void {
let lo = 0, hi = this.a.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (this.a[mid]! < num) lo = mid + 1;
else hi = mid;
}
this.a.splice(lo, 0, num);
}
findMedian(): number {
const n = this.a.length;
const m = n >> 1;
return n % 2 ? this.a[m]! : (this.a[m - 1]! + this.a[m]!) / 2;
}
}
/** Naive median: keep a sorted array (O(n) insert). */
export class MedianFinderSorted {
private a: number[] = [];
addNum(num: number): void {
let lo = 0, hi = this.a.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (this.a[mid]! < num) lo = mid + 1;
else hi = mid;
}
this.a.splice(lo, 0, num);
}
findMedian(): number {
const n = this.a.length;
const m = n >> 1;
return n % 2 ? this.a[m]! : (this.a[m - 1]! + this.a[m]!) / 2;
}
}
Reflection
- Which pattern gave this away within 90 seconds?
- What changed from the standard template?
- What would break the current solution?