Description
295. Find Median from Data Stream
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 is3. - For example, for
arr = [2,3], the median is(2 + 3) / 2 = 2.5.
Implement the MedianFinder class:
MedianFinder()initializes theMedianFinderobject.void addNum(int num)adds the integernumfrom the data stream to the data structure.double findMedian()returns the median of all elements so far. Answers within 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 (i.e., (1 + 2) / 2)
medianFinder.addNum(3); // arr = [1, 2, 3]
medianFinder.findMedian(); // return 2.0
Constraints:
-10^5 <= num <= 10^5- There will be at least one element in the data structure before calling
findMedian. - At most calls will be made to
addNumandfindMedian.
Brute Force Approach: Insertion Sort / List Sorting
Intuition
Maintain a sorted dynamic array. Every time a new number arrives, insert it into its sorted position using binary search. To find the median, retrieve the element(s) at index n / 2.
import java.util.*;
class MedianFinder {
private List<Integer> list;
public MedianFinder() {
list = new ArrayList<>();
}
public void addNum(int num) {
int index = Collections.binarySearch(list, num);
if (index < 0) {
index = -(index + 1);
}
list.add(index, num); // Insertion takes O(N) shift time
}
public double findMedian() {
int n = list.size();
if (n % 2 == 1) {
return list.get(n / 2);
} else {
return (list.get(n / 2 - 1) + list.get(n / 2)) / 2.0;
}
}
}
Complexity
- Time Complexity:
addNum: — Binary search takes , but array element shifting takes .findMedian: — Direct index access.
- Space Complexity: — Stores all elements in an array list.
Most Optimized Solution: Two Heaps (Max-Heap + Min-Heap)
Intuition
Divide all numbers into two equal-sized halves:
small(Max-Heap): Stores the smaller half of numbers (the largest value in this half is at the top).large(Min-Heap): Stores the larger half of numbers (the smallest value in this half is at the top).
By balancing both heaps:
- Every element in
smallis every element inlarge. - The median is always accessible in time at the top of the heaps.
import java.util.PriorityQueue;
import java.util.Collections;
class MedianFinder {
private PriorityQueue<Integer> small; // Max-Heap for the smaller half
private PriorityQueue<Integer> large; // Min-Heap for the larger half
public MedianFinder() {
small = new PriorityQueue<>(Collections.reverseOrder());
large = new PriorityQueue<>();
}
public void addNum(int num) {
// Step 1: Add to small heap
small.add(num);
// Step 2: Ensure max(small) <= min(large)
large.add(small.poll());
// Step 3: Keep small size >= large size (small can have at most 1 extra element)
if (large.size() > small.size()) {
small.add(large.poll());
}
}
public double findMedian() {
if (small.size() > large.size()) {
return small.peek();
} else {
return (small.peek() + large.peek()) / 2.0;
}
}
}
Complexity
-
Time Complexity:
-
addNum: — Heap push and pop operations. -
findMedian: — Peek operations at the top of the heaps. -
Space Complexity: — Stores all numbers across both heaps.
Follow-Up Solutions
1. All numbers in range [0, 100]
Use a frequency array of size 101 (count[101]) and a total element counter:
addNum: Incrementcount[num]in time.findMedian: Iterate throughcount[101]to find the 50th percentile index in time and space.
2. 99% of numbers in range [0, 100]
Use a frequency array of size 101 for values in [0, 100] and two counters/structures for values strictly < 0 and > 100. Since 99% of elements lie within [0, 100], the median will almost certainly fall within [0, 100], maintaining average time.
Easy Memory Rule
“Max-Heap for smaller half, Min-Heap for larger half. Keep
smallsizelargesize.”
1. What is a Heap?
A Heap is a tree-based data structure optimized for fast access to either the highest or lowest value in a dataset.
- Max-Heap: The parent node is always greater than or equal to its children. The largest element sits at the root (
top). - Min-Heap: The parent node is always smaller than or equal to its children. The smallest element sits at the root (
top).
Key Operations:
peek()/top()(retrieve max/min without removing)add()/poll()(insert or remove the top element and re-balance tree)
2. The Two Heaps Used in Find Median from Data Stream
To find the median in time, we divide all numbers into two equal-sized halves:
-
small(Max-Heap): Stores the smaller half of numbers.- Purpose: Holds the left half of the sorted sequence. Calling
small.peek()gives the largest number of this lower half.
- Purpose: Holds the left half of the sorted sequence. Calling
-
large(Min-Heap): Stores the larger half of numbers.- Purpose: Holds the right half of the sorted sequence. Calling
large.peek()gives the smallest number of this upper half.
- Purpose: Holds the right half of the sorted sequence. Calling
Core Invariant:
Every element in small must be every element in large.
3. Step-by-Step Method Breakdown
import java.util.PriorityQueue;
import java.util.Collections;
class MedianFinder {
private PriorityQueue<Integer> small; // Max-Heap
private PriorityQueue<Integer> large; // Min-Heap
public MedianFinder() {
small = new PriorityQueue<>(Collections.reverseOrder());
large = new PriorityQueue<>();
}
public void addNum(int num) {
small.add(num);
large.add(small.poll());
if (large.size() > small.size()) {
small.add(large.poll());
}
}
public double findMedian() {
if (small.size() > large.size()) {
return small.peek();
} else {
return (small.peek() + large.peek()) / 2.0;
}
}
}
Method 1: MedianFinder() (Constructor)
- Goal: Initialize both heaps.
smallusesCollections.reverseOrder()so that standard JavaPriorityQueueacts as a Max-Heap.largeuses default behavior, acting as a Min-Heap.
Method 2: addNum(int num)
- Goal: Add
numand keep both heaps sorted relative to each other and balanced in size. - Step 1 (
small.add(num)): Pushnuminto thesmallMax-Heap. - Step 2 (
large.add(small.poll())): Remove the largest value fromsmalland push it tolarge. This guarantees that all elements insmallare all elements inlarge. - Step 3 (
if (large.size() > small.size()) small.add(large.poll())): Iflargehas more elements thansmall, move its smallest element back tosmall. This ensuressmallcarries the odd extra element whenever total count is odd (small.size()is either equal to or greater thanlarge.size()).
Method 3: findMedian()
-
Goal: Return the median in time.
-
Odd total elements (
small.size() > large.size()): -
The median is the single extra middle element stored at the top of
small(small.peek()). -
Even total elements (
small.size() == large.size()): -
The median is the average of the two middle elements: the largest of the lower half (
small.peek()) and the smallest of the upper half (large.peek()).
Comparison
1. Odd Count: [1, 2, 5, 8, 9] (5 elements)
- There is a physical middle element:
5. - Median =
5
2. Even Count: [1, 2, 5, 8] (4 elements)
- The two middle elements are
2and5. - Neither
2nor5alone is the middle line.- If you pick
2, only 1 element is smaller (1), but 2 elements are bigger (5, 8). That’s unbalanced! - If you pick
5, 2 elements are smaller (1, 2), but only 1 element is bigger (8). Also unbalanced!
- If you pick
- To find the exact midpoint of the gap between
2and5, you calculate their average:
The Role of the Average
The average () creates a synthetic “center point”:
- 2 elements are smaller than
3.5(1, 2). - 2 elements are larger than
3.5(5, 8).
So for an even number of elements, “average of the two middle elements” is just the mathematical rule used to produce the single dividing value.