295. Find Median from Data Stream
At a Glance
- Topic: Two Pointers
- Pattern: Analyze Pattern
- Difficulty: Hard
- LeetCode: 295
Problem Statement
The median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values.
For example, for arr = [2,3,4], the median is 3.
For example, for arr = [2,3], the median is (2 + 3) / 2 = 2.5.Implement the MedianFinder class:
MedianFinder() initializes the MedianFinder object.
void addNum(int num) adds the integer num from the data stream to the data structure.
double findMedian() returns the median of all elements so far. Answers within 10-5 of the actual answer will be accepted.Example 1:
Input ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"] [[], [1], [2], [], [3], []] Output [null, null, null, 1.5, null, 2.0]
Explanation MedianFinder medianFinder = new MedianFinder(); medianFinder.addNum(1); // arr = [1] medianFinder.addNum(2); // arr = [1, 2] medianFinder.findMedian(); // return 1.5...
Approach & Solution Steps
- Brute force — buffer all values, sort on every
findMedian—O(n log n)per query. - Acceptable — balanced BST / order-statistic tree (policy-based or
TreeMapmultiset) —O(log n)insert +O(log n)median if supported. - Optimal — two heaps: max-heap for smaller half, min-heap for larger half, sizes differ by at most one —
O(log n)addNum,O(1)findMedianfor standard heap APIs.
Optimal JS Solution
class MinHeap {
constructor() {
this.values = [];
}
push(value) {
this.values.push(value);
this.bubbleUp(this.values.length - 1);
}
pop() {
const top = this.values[0];
const last = this.values.pop();
if (this.values.length > 0) {
this.values[0] = last;
this.bubbleDown(0);
}
return top;
}
peek() {
return this.values[0];
}
get size() {
return this.values.length;
}
bubbleUp(index) {
while (index > 0) {
const parentIndex = Math.floor((index - 1) / 2);
if (this.values[parentIndex] <= this.values[index]) {
break;
}
[this.values[parentIndex], this.values[index]] = [
this.values[index],
this.values[parentIndex],
];
index = parentIndex;
}
}
bubbleDown(index) {
const length = this.values.length;
while (true) {
const leftIndex = index * 2 + 1;
const rightIndex = index * 2 + 2;
let smallest = index;
if (
leftIndex < length &&
this.values[leftIndex] < this.values[smallest]
) {
smallest = leftIndex;
}
if (
rightIndex < length &&
this.values[rightIndex] < this.values[smallest]
) {
smallest = rightIndex;
}
if (smallest === index) {
break;
}
[this.values[index], this.values[smallest]] = [
this.values[smallest],
this.values[index],
];
index = smallest;
}
}
}
class MaxHeap {
constructor() {
this.values = [];
}
push(value) {
this.values.push(value);
this.bubbleUp(this.values.length - 1);
}
pop() {
const top = this.values[0];
const last = this.values.pop();
if (this.values.length > 0) {
this.values[0] = last;
this.bubbleDown(0);
}
return top;
}
peek() {
return this.values[0];
}
get size() {
return this.values.length;
}
bubbleUp(index) {
while (index > 0) {
const parentIndex = Math.floor((index - 1) / 2);
if (this.values[parentIndex] >= this.values[index]) {
break;
}
[this.values[parentIndex], this.values[index]] = [
this.values[index],
this.values[parentIndex],
];
index = parentIndex;
}
}
bubbleDown(index) {
const length = this.values.length;
while (true) {
const leftIndex = index * 2 + 1;
const rightIndex = index * 2 + 2;
let largest = index;
if (
leftIndex < length &&
this.values[leftIndex] > this.values[largest]
) {
largest = leftIndex;
}
if (
rightIndex < length &&
this.values[rightIndex] > this.values[largest]
) {
largest = rightIndex;
}
if (largest === index) {
break;
}
[this.values[index], this.values[largest]] = [
this.values[largest],
this.values[index],
];
index = largest;
}
}
}
class MedianFinder {
constructor() {
this.lowerMax = new MaxHeap();
this.upperMin = new MinHeap();
}
addNum(value) {
if (
this.lowerMax.size === 0 ||
value <= this.lowerMax.peek()
) {
this.lowerMax.push(value);
} else {
this.upperMin.push(value);
}
if (this.lowerMax.size > this.upperMin.size + 1) {
this.upperMin.push(this.lowerMax.pop());
} else if (this.upperMin.size > this.lowerMax.size + 1) {
this.lowerMax.push(this.upperMin.pop());
}
}
findMedian() {
if (this.lowerMax.size > this.upperMin.size) {
return this.lowerMax.peek();
}
if (this.upperMin.size > this.lowerMax.size) {
return this.upperMin.peek();
}
return (this.lowerMax.peek() + this.upperMin.peek()) / 2;
}
}Edge Cases & Pitfalls
- Always consider empty or null inputs.
- Watch out for off-by-one index errors.
Mark this page when you finish learning it.
Last updated on
Spotted something unclear or wrong on this page?