Find Median from Data Stream
Balance a max-heap of the low half against a min-heap of the high half.
Approach
Keep a max-heap for the smaller half and a min-heap for the larger half, rebalancing so their sizes differ by at most one. The median is the top of the larger heap (odd count) or the average of both tops (even). Each insert is O(log n); median is O(1).
Time complexity
O(log n) add, O(1) median
Space complexity
O(n)
Common mistake
Letting the heaps drift out of balance, so the tops no longer straddle the true median.
Interactive walkthrough — step through it below, free and without an account
Problem
Build a data structure that accepts integers incrementally and reports the median of the values currently stored. The median is the middle value when the numbers are sorted, or the average of the two middle values when the count is even. Support insertion and median queries efficiently.
Examples
Input: addNum(5), addNum(15), findMedian(), addNum(1), findMedian(), addNum(3), findMedian()
Output: 5.0, 10.0, 5.0
Input: addNum(2), addNum(8), addNum(4), findMedian()
Output: 4.0
Problem
Problem
Build a data structure that accepts integers incrementally and reports the median of the values currently stored. The median is the middle value when the numbers are sorted, or the average of the two middle values when the count is even. Support insertion and median queries efficiently.
Problem and Goal
Learning goal: Understand why a stream of numbers needs a specialized structure instead of repeatedly sorting all values.
Core idea
Numbers arrive one at a time, so the data structure must preserve enough order information for a fast median query. Sorting every value after each insertion would repeatedly redo work. The target is O(log n) for addNum() and O(1) for findMedian().
Algorithm plan
- Maintain a partition between the lower half and upper half of the values.
- Store the lower half in a max-heap so its largest value is immediately available.
- Store the upper half in a min-heap so its smallest value is immediately available.
- Rebalance after each insertion.
- Read one or two heap tops to calculate the median.
Micro-example
For [1, 3, 5, 15], the lower half is [1, 3] and the upper half is [5, 15]. The two boundary values are 3 and 5, so the median is 4.
Pitfalls
- Treating either heap as a fully sorted array is unnecessary.
- Forgetting that the two heaps together contain every inserted number.
- Returning a single top when the total count is even.
Recap
- The median depends only on the boundary between two halves.
- Two heaps expose those boundaries efficiently.
- Balance is part of the data structure invariant.
Problem and Goal
Keep the middle visible
Split the stream into a lower half and an upper half. The max-heap top is the largest lower value; the min-heap top is the smallest upper value.
Fast median queries come from maintaining the partition, not from sorting the entire stream.
Key Idea: Two Balanced Heaps
Learning goal: See how two opposite heap orders make the median accessible.
Core idea
Let low be a max-heap containing the smaller half and high be a min-heap containing the larger half. Every value in low must be less than or equal to every value in high. Because the heaps expose opposite extremes, low.top and high.top straddle the median.
Algorithm plan
- If
lowis empty ornum <= low.top, insert intolow. - Otherwise insert into
high. - If
lowhas more than one extra element, movelow.toptohigh. - If
highis larger thanlow, movehigh.toptolow. - Use the larger heap's top for an odd count; average both tops for an even count.
Key insight
The heaps do not need equal sizes. The useful invariant is size(low) == size(high) or size(low) == size(high) + 1.
Micro-example
After inserting [1, 3, 5], low contains [3, 1] and high contains [5]. Since low has one extra item, low.top = 3 is the median.
Pitfalls
- A max-heap is required for
low; a min-heap would expose the wrong boundary. - A min-heap is required for
highso its smallest upper value is visible. - The ordering invariant and size invariant must both hold.
Recap
low.topis the lower boundary.high.topis the upper boundary.- Balanced sizes determine whether one top or two tops represent the median.
Key Idea: Two Balanced Heaps
Every value in the max-heap `low` is no greater than every value in the min-heap `high`.
`low = [3, 1]`, `high = [5, 15]`
Insertion and Rebalancing
Learning goal: Translate the two-heap idea into a reliable insertion procedure.
Core idea
Insertion has two stages: place the new value near its correct side, then repair the size balance. A heap insertion costs O(log n), and moving a root between heaps also costs O(log n). The rebalancing rules are simple because the allowed size difference is only one.
Algorithm plan
- Compare
numwithlow.top; chooselowfor smaller values andhighotherwise. - If
low.size > high.size + 1, move the maximum fromlowintohigh. - If
high.size > low.size, move the minimum fromhighintolow. - For an odd total, return
low.topbecauselowis allowed to have the extra value. - For an even total, return
(low.top + high.top) / 2.
addNum(num):
insert num into low or high
if low has two more values than high:
move low.top to high
if high has more values than low:
move high.top to lowMicro-example
Insert 3 after [1, 5, 15]. It belongs in low, temporarily making low contain three values and high one. Moving low.top = 5 to high restores sizes 2 and 2.
Pitfalls
- Rebalance after every insertion, not only before a query.
- Move the correct root: maximum from
low, minimum fromhigh. - Keep
lowas the heap allowed to contain the extra item.
Recap
- Insert, then rebalance.
- Each insertion remains logarithmic.
- The size convention makes odd-count handling deterministic.
Insertion and Rebalancing
| Approach | addNum | findMedian | Trade-off |
|---|---|---|---|
| Sort after every insert | O(n log n) | O(1) | Repeats nearly all ordering work |
| Unsorted array scan | O(1) | O(n) | Median query becomes expensive |
| Two balanced heaps | O(log n) | O(1) | Maintains only the needed boundaries |
End-to-End Walkthrough
Learning goal: Trace every insertion in one stream and connect heap changes to the returned median.
Core idea
Use the example stream 5, 15, 1, 3. After every insertion, the max-heap low represents the lower half and the min-heap high represents the upper half. The heaps may have different internal arrangements, but their roots and sizes must satisfy the invariants.
Algorithm plan
- Insert
5intolow; it is the only value, so the median is5. - Insert
15intohigh; equal sizes make the median(5 + 15) / 2 = 10. - Insert
1intolow; its extra item makeslow.top = 5the median. - Insert
3intolow; the size gap becomes two, so move5tohigh. - The final boundary values are
3and5, producing median4.
Micro-example
Final state: low contains [3, 1]; high contains [5, 15]. The sorted stream is [1, 3, 5, 15], and the heap roots expose the middle pair without sorting.
Pitfalls
- The heap array's internal order is not a sorted list.
- A transfer during rebalancing changes both the partition and the roots.
- Always compute the median from the current state, not from an earlier snapshot.
Recap
- Four inserts require only local heap operations.
- Rebalancing after
3is the decisive step. - The final answer is
4, matching the sorted interpretation.
End-to-End Walkthrough
A synchronized trace of the max-heap lower half and min-heap upper half for the canonical example.
Pitfalls and Edge Cases
Learning goal: Diagnose the failure mode that makes a two-heap median incorrect.
Core idea
Heap order alone is not enough. If one heap grows much larger than the other, its top may represent a value far from the true middle. The heaps must remain partitioned and balanced after every addNum() call, even if the next operation is another insertion rather than a query.
Algorithm plan
- Decide the destination using the current boundary, usually
low.top. - Repair a two-item size advantage by moving one root.
- Confirm that
low.top <= high.topwhenever both heaps are nonempty. - Handle duplicates exactly like ordinary values.
- Use numeric division for even counts.
Micro-example
Suppose values [1, 2, 100, 101] are incorrectly stored with three values in low and one in high. Returning low.top = 2 would be wrong; the true median is (2 + 100) / 2 = 51.
Common pitfall
Letting the heaps drift out of balance means their tops no longer straddle the true median. Always rebalance immediately after insertion.
Pitfalls
- Moving the wrong root can violate the partition order.
- Assuming the larger heap is always
highconflicts with the chosen size convention. - Integer division can truncate an even-count median in some languages.
Recap
- Balance is correctness, not just optimization.
- Check both ordering and size invariants.
- Duplicates and negative values need no special algorithm.
Pitfalls and Edge Cases
After processing [1, 2, 100, 101], which state correctly supports a constant-time median query?
Complexity and Wrap-up
Learning goal: Connect the heap operations to the target time and space bounds.
Core idea
Each number is stored in exactly one heap, so the total space is linear. Insertion into a binary heap takes O(log n), and a rebalance moves at most one root, also taking O(log n). Reading heap tops is direct, so findMedian() takes O(1).
Algorithm plan
- Charge the destination insertion to
O(log n). - Charge a possible root transfer to another
O(log n)operation. - Treat median lookup as constant work because heap tops are already exposed.
- Store all
nstream values across the two heaps.
Micro-example For 50,000 insertions, the structure never sorts all 50,000 values during a query. Each insertion adjusts a logarithmic-height heap, while each median request reads one or two roots.
Pitfalls
- Saying
findMedian()isO(log n)overlooks that no heap mutation is needed. - Calling the total space
O(log n)ignores that every input remains stored. - Amortized language is unnecessary here: each insertion has a logarithmic worst-case bound.
Recap
addNum:O(log n)time.findMedian:O(1)time.- Storage:
O(n)space. - The central invariant keeps the middle visible at all times.
Complexity and Wrap-up
The final performance profile of the two-heap design.
Now try it on your own problem
Paste any LeetCode problem — or one of your own — and codebrew builds a walkthrough like this one for it.
Build a lesson from any problem
