Recommended resources
The freely available resources this lesson will direct you to.
- Find Median from Data Stream - Heap & Priority Queue - Leetcode 295www.youtube.com
- Data Structures: Solve 'Find the Running Median' Using Heapswww.youtube.com
- 295. Find Median from Data Stream - In-Depth Explanationalgo.monster
- (LeetCode) Find Median from Data Stream: 4 Approaches Explainedlevelup.gitconnected.com
Welcome back. In the previous lesson, we established the "why" behind the two-heaps pattern, identifying it as the ideal structure for problems that require partitioning a dataset and efficiently accessing the boundary elements, like finding a running median.
Now, we shift our focus to the "how." Today's lesson is all about implementation. Your goal is to implement the two-heaps pattern to find the median of a data stream. We will translate the conceptual model of a max-heap and a min-heap working in tandem into robust, interview-ready code. This involves mastering the logic for adding new numbers while maintaining the crucial heap invariants.
Let's begin by recalling the core structure:
- A max-heap, let's call it
small_half, to store the smaller half of the numbers. - A min-heap, let's call it
large_half, to store the larger half of the numbers.
To make this system work, we must enforce two invariants at all times:
- Value Invariant: Every number in
small_halfmust be less than or equal to every number inlarge_half. - Size Invariant: The sizes of the two heaps must be either equal or differ by at most one.
With these rules in place, the median is always accessible in time from the heaps' top elements. The main challenge lies in designing the addNum method to preserve these invariants efficiently.
The Algorithm: A Three-Step Process
A robust implementation of addNum can be broken down into a clean, three-step process for each incoming number: insertion, rebalancing values, and rebalancing sizes. A good way to conceptualize this is to think of three distinct functions or logical blocks, as suggested in high-quality software design.

Let's dive into the most elegant and common implementation strategy for adding a new number.
295. Find Median from Data Stream - In-Depth Explanation
This Algo.monster article provides a fantastic, detailed guide on the implementation logic. We'll focus on the core addNum and findMedian strategies.
First, read the intuition section to refresh the core idea. Then, carefully study the section "Solution Approach". Pay close attention to the descriptions for "Adding Numbers (addNum)" and "Finding Median (findMedian)". The addNum logic described here is particularly clever and is a standard pattern you should aim to master. It combines insertion and value-rebalancing into a single, efficient flow.
Let's break down the logic from the reading.
The addNum(num) Method
Instead of first deciding which heap a new number belongs to, a more foolproof approach is to always add it to one heap and then immediately balance the values. Let's say our convention is that large_half (the min-heap) can have at most one more element than small_half.
-
Push to
small_halfand then move tolarge_half: We first push the newnumintosmall_half(the max-heap). Then, we immediately pop the largest element fromsmall_half(which could be thenumwe just added) and push it intolarge_half. This two-step dance elegantly ensures the value invariant is always maintained. After this, we know for certain thatsmall_half.max() <= large_half.min(). -
Rebalance Sizes: The previous step always adds one element to
large_half. This might disrupt the size invariant. Iflarge_halfnow has more than one extra element compared tosmall_half, we restore balance by moving the smallest element fromlarge_halfback tosmall_half.
This sequence of operations ensures both invariants hold true after every addition, with a time complexity of due to the heap pushes and pops.
The findMedian() Method
This part is straightforward once the heaps are correctly maintained:
- If the heaps have the same size (
len(small_half) == len(large_half)), the total number of elements is even. The median is the average of the two middle elements:(small_half.max() + large_half.min()) / 2. - If the heaps have different sizes, the total is odd. By our convention,
large_halfwill be the larger heap, so its top element is the median:large_half.min().
From Theory to Python Code
Now, let's address the practical details of implementing this in Python. As you may know, Python's heapq library only provides a min-heap. How do we create the max-heap we need for small_half?
The standard trick is to store the negative of the numbers. A min-heap operating on negative values effectively behaves like a max-heap for the original positive values. For example, min(-5, -2, -8) is -8, which corresponds to max(5, 2, 8).
This requires careful handling:
- When you push a number into the
small_half(our max-heap), you push its negative. - When you peek at or pop from
small_half, you must negate the result to get the original number back.
The following video provides an excellent walkthrough of the Python implementation, paying special attention to this negation trick.
Find Median from Data Stream - Heap & Priority Queue - Leetcode 295
This NeetCode video, which we used for concepts before, also contains a clear coding walkthrough.
Watch the segment from this timestamp. The presenter codes the MedianFinder class in Python from scratch. Focus on: How he uses -num when pushing to the small heap. How he negates the value again when popping from small to move it to large. How the final median calculation handles the negated value from the small heap's top element.
A Clean Implementation
Combining the robust logic from Algo.monster with the Python-specifics from the video, we arrive at a clean and efficient implementation. Let's look at the final code.
295. Find Median from Data Stream - In-Depth Explanation
This section provides the complete, commented Python code for the MedianFinder class.
Please review the Python code block. Compare this implementation to the logic you just learned. Notice the concise line: heappush(self.min_heap, -heappushpop(self.max_heap, -num)). This single line corresponds to our "push to small_half then move to large_half" step, and it's a highly efficient way to write it.
Solidifying Your Understanding: Common Pitfalls
Since your goal is a deep and practical understanding for interview settings, it's vital to be aware of common mistakes. A correct but fragile solution is not enough; you need a robust one.
295. Find Median from Data Stream - In-Depth Explanation
The same Algo.monster resource concludes with an excellent section on what can go wrong.
Read the entire Common Pitfalls section. This covers critical errors related to: Balancing Logic: Why simply checking sizes isn't enough. Negation: The consequences of forgetting to negate values for the max-heap. Division: Integer vs. float division when calculating the average. Edge Cases: Accessing an empty heap.
Being mindful of these pitfalls is what separates a basic understanding from a mastery of the pattern.
Conclusion
In this lesson, you have moved from the conceptual understanding of the two-heaps pattern to a detailed, practical implementation for finding the median of a data stream. We focused on establishing and maintaining the core value and size invariants, which are the keys to a correct solution.
Key Takeaways:
- A robust
addNummethod can be implemented by always pushing to one heap, immediately moving the top to the other heap to maintain the value invariant, and then rebalancing sizes. - In Python, a max-heap is simulated by storing negative values in a min-heap, which requires careful negation on insertion and retrieval.
- The
findMedianmethod is a simple operation that checks heap sizes to determine whether to return the top of the larger heap or the average of both tops. - The overall time complexity is for
addNumand forfindMedian, with space complexity to store the numbers.
You are now equipped to implement one of the most classic and important heap-based algorithms. In our next lesson, we will tackle a more complex variant: what if you need to remove elements as well? This will lead us to the "Sliding Window Median" problem and introduce techniques like lazy deletion to handle element removal from heaps.