Median of Data Stream Using Two Heaps in DSA Typescript - Time & Space Complexity
We want to understand how fast we can find the middle value when numbers keep coming in one by one.
How does the time to add a number and get the middle change as more numbers arrive?
Analyze the time complexity of the following code snippet.
class MedianFinder {
private maxHeap: number[] = [] // smaller half
private minHeap: number[] = [] // larger half
addNum(num: number): void {
if (!this.maxHeap.length || num <= -this.maxHeap[0]) {
this.maxHeapPush(-num)
} else {
this.minHeapPush(num)
}
this.balanceHeaps()
}
findMedian(): number {
if (this.maxHeap.length > this.minHeap.length) {
return -this.maxHeap[0]
} else {
return (-this.maxHeap[0] + this.minHeap[0]) / 2
}
}
private maxHeapPush(val: number) { /* push and heapify */ }
private minHeapPush(val: number) { /* push and heapify */ }
private balanceHeaps() { /* move elements to keep sizes balanced */ }
}
This code keeps two heaps to quickly find the median as numbers are added.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Adding a number involves inserting into a heap and balancing heaps.
- How many times: Each addNum call does these operations once per number added.
Each new number causes a few heap operations that take time related to the heap size.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 insertions and balancing steps, each small. |
| 100 | About 100 insertions, each a bit longer due to bigger heaps. |
| 1000 | About 1000 insertions, each slightly longer but still efficient. |
Pattern observation: The time per insertion grows slowly as heaps grow, not linearly.
Time Complexity: O(log n)
This means adding each number and updating the median takes time that grows slowly with the total numbers added.
[X] Wrong: "Adding a number takes constant time because it's just one step."
[OK] Correct: Actually, adding to a heap requires rearranging elements to keep order, which takes time growing with heap size.
Knowing how to keep track of medians efficiently shows you can handle data that changes over time, a useful skill in many real problems.
"What if we used a sorted array instead of heaps? How would the time complexity change?"
Practice
Solution
Step 1: Understand the role of two heaps
The max heap stores the smaller half of the numbers, and the min heap stores the larger half.Step 2: Explain how this helps find the median
By balancing these heaps, the median is either the top of one heap or the average of tops of both heaps.Final Answer:
To efficiently keep track of the lower half and upper half of the numbers separately -> Option CQuick Check:
Two heaps split data halves = efficient median [OK]
- Thinking heaps sort all numbers fully
- Using one heap only
- Confusing heaps with arrays
Solution
Step 1: Understand max heap comparator
Max heap comparator returns positive if first is less than second, so b - a creates max heap.Step 2: Check each option
const maxHeap = new PriorityQueue<number>((a, b) => b - a); uses (b - a) which is correct for max heap; others do not create max heap order.Final Answer:
const maxHeap = new PriorityQueue<number>((a, b) => b - a); -> Option AQuick Check:
Comparator b - a = max heap [OK]
- Using a - b for max heap (creates min heap)
- Not providing comparator
- Using addition in comparator
[5, 15, 1, 3], what is the median after all insertions using two heaps?Solution
Step 1: Insert numbers and balance heaps
Insert 5 -> maxHeap=[5] (top=5), minHeap=[]; Insert 15 -> minHeap=[15] (top=15), sizes 1:1; Insert 1 -> maxHeap=[5,1] (top=5), sizes 2:1; Insert 3 -> maxHeap=[5,3,1] (top=5), sizes 3:1 -> balance (max.size > min.size+1), move max.top=5 to minHeap -> maxHeap=[3,1] (top=3), minHeap=[5,15] (top=5), sizes 2:2.Step 2: Calculate median from heaps
Sizes equal, median = (maxHeap.top + minHeap.top)/2 = (3 + 5)/2 = 4.Final Answer:
4 -> Option BQuick Check:
Median of [5,15,1,3] = 4 [OK]
- Taking median as top of one heap only
- Not balancing heaps after insertion
- Wrong average calculation
if (num <= maxHeap.peek()) {
maxHeap.enqueue(num);
} else {
minHeap.enqueue(num);
}
// Balance heaps
if (maxHeap.size() > minHeap.size() + 1) {
minHeap.enqueue(maxHeap.dequeue());
} else if (minHeap.size() > maxHeap.size()) {
maxHeap.enqueue(minHeap.dequeue());
}
What is the bug in this code?Solution
Step 1: Analyze peek() usage on maxHeap
If maxHeap is empty, calling peek() causes error because no element exists yet.Step 2: Identify missing empty check
Code should check if maxHeap is empty before peek() or handle initial insertion separately.Final Answer:
It does not handle empty maxHeap before peek() call -> Option DQuick Check:
Empty heap peek() causes runtime error [OK]
- Assuming heaps always have elements
- Mixing enqueue and push methods
- Balancing heaps incorrectly
Solution
Step 1: Understand removal challenge in heaps
Heaps do not support efficient arbitrary removal, so direct removal is costly.Step 2: Explain lazy removal with hash map
Mark elements as removed in a hash map and skip them during heap operations to simulate removal efficiently.Step 3: Compare other options
Balanced trees support removal but are more complex; arrays are inefficient; queues do not maintain order.Final Answer:
Use two heaps with a hash map to mark removed elements lazily -> Option AQuick Check:
Lazy removal with hash map + heaps = efficient median update [OK]
- Trying direct removal from heaps
- Using arrays causing slow operations
- Ignoring complexity of balanced trees
Ready to prove it?
Pass the test and this pattern moves unknown
