Find Median from Data Stream - Watch the Algorithm Execute, Step by Step
Watching the algorithm step-by-step reveals how the two heaps maintain a balanced state to efficiently find the median at any point in the data stream.
Step 1/10
·Active fill★Answer cell
setup
insert
small_top
-1
0
insert
small_top
-1
0
large_top
2
1
compare
small_top
-1
0
large_top
2
1
compare
small_top
-1
0
large_top
2
1
Result: 1.5
insert
small_top
-1
0
large_top
2
1
3
2
Result: 1.5
rebalance
small_top
-2
0
-1
1
large_top
3
2
Result: 1.5
compare
small_top
-2
0
-1
1
large_top
3
2
Result: 2
record
small_top
-1
0
large_top
2
1
Result: 1.5
record
small_top
-2
0
-1
1
large_top
3
2
Result: 2
Key Takeaways
✓ Two heaps maintain a balanced partition of the data stream to efficiently compute median.
This balance is hard to see from code alone but is clear when watching heaps grow and shrink.
✓ Median calculation depends on heap sizes: average of tops if equal, top of larger heap if odd count.
Visualizing heap sizes and top elements clarifies why median formulas differ.
✓ Rebalancing moves elements between heaps to maintain size constraints after each insertion.
Seeing the actual element moved during rebalance helps understand the heap size invariants.
Practice
(1/5)
1. You need to design a data structure that supports adding intervals, removing intervals, and querying whether a given range is fully covered by the added intervals. Which approach guarantees efficient updates and queries with logarithmic time complexity per operation?
easy
A. Use a balanced binary search tree (e.g., TreeMap) to store and merge intervals, enabling logarithmic time operations.
B. Use a brute force list of intervals and scan all intervals for each operation.
C. Use dynamic programming to precompute coverage for all possible ranges.
D. Use a greedy algorithm that always merges intervals only when queried.
Solution
Step 1: Understand the problem requirements
The data structure must support add, remove, and query operations on intervals efficiently.
Step 2: Evaluate approaches
Brute force scanning is O(n) per operation, which is inefficient. DP is not suitable for dynamic interval updates. Greedy merging only on query delays merging and leads to inefficient queries. Balanced BST with interval merging maintains sorted intervals and merges overlapping ones, supporting O(log n) operations.
Final Answer:
Option A -> Option A
Quick Check:
Balanced BST with merging -> O(log n) per operation [OK]
Hint: Balanced BST with merging enables efficient interval operations [OK]
Common Mistakes:
Thinking DP or greedy solves dynamic interval updates efficiently
2. Consider this buggy snippet of the optimal Twitter design's getNewsFeed method:
def getNewsFeed(self, userId: int) -> list:
users = self.followees.get(userId, set()).copy()
heap = [] # max heap by time
for u in users:
head = self.userTweets.get(u, None)
if head:
heapq.heappush(heap, (-head.time, head))
result = []
while heap and len(result) < 10:
time, node = heapq.heappop(heap)
result.append(node.tweetId)
if node.next:
heapq.heappush(heap, (-node.next.time, node.next))
return result
What is the subtle bug in this code?
medium
A. The user's own tweets are not included because userId is not added to users set.
B. The heap is used as a min heap instead of max heap by pushing negative times.
C. The linked list nodes are not updated correctly after popping from the heap.
D. The followees set is copied unnecessarily causing performance overhead.
Solution
Step 1: Check inclusion of user's own tweets
The code gets followees but never adds the userId itself to the users set, so user's own tweets are excluded.
Step 2: Verify heap usage and linked list traversal
Negative times are correctly used for max heap behavior; linked list next pointers are used properly.
Final Answer:
Option A -> Option A
Quick Check:
Missing userId in users set causes incorrect feed [OK]
Hint: User's own tweets must be explicitly included in feed [OK]
Common Mistakes:
Forgetting to add userId to followees set
Misusing heap sign for max heap
3. What is the amortized time complexity of the put operation in the optimal LFU Cache implementation using a frequency map of doubly linked lists and hash maps?
medium
A. O(n), where n is the number of keys in the cache
B. O(f), where f is the number of distinct frequencies
C. O(log n), due to maintaining frequency order
D. O(1), amortized constant time
Solution
Step 1: Analyze data structures used
Hash maps provide O(1) access to keys and frequencies. Doubly linked lists allow O(1) insertion and deletion of keys within frequency groups.
Step 2: Consider operations in put
Eviction and frequency updates involve O(1) operations on hash maps and linked lists. Frequency increments and min frequency updates are also O(1).
Final Answer:
Option D -> Option D
Quick Check:
Optimal LFU cache achieves O(1) amortized put [OK]
Hint: Hash maps + linked lists enable O(1) put [OK]
Common Mistakes:
Assuming O(n) due to scanning keys
Thinking frequency updates cost O(log n)
Confusing distinct frequencies with n
4. What is the time complexity of the divide and conquer approach to the Skyline Problem when given n buildings? Assume merging two skylines of total length m takes O(m) time.
medium
A. O(n²)
B. O(n)
C. O(n log n)
D. O(n * w) where w is the width of the skyline
Solution
Step 1: Identify divide and conquer recurrence
The algorithm splits buildings into halves recursively, then merges skylines in O(m) time where m is proportional to n.
Step 2: Solve recurrence and analyze merge cost
Recurrence is T(n) = 2T(n/2) + O(n), which solves to O(n log n). The width w does not affect complexity here.
Final Answer:
Option C -> Option C
Quick Check:
Divide and conquer with linear merge per level -> O(n log n) [OK]
5. Suppose you want to extend the LRU Cache to support a "reuse" operation that marks an existing key as recently used without retrieving its value. Which modification to the optimal LRU Cache implementation is correct to support this efficiently?
hard
A. On reuse, remove the key from the cache and re-insert it with the same value.
B. Do nothing; reuse is equivalent to get, so no code change is needed.
C. Use a queue instead of a doubly linked list to track usage order for easier reuse.
D. Add a new method that calls _remove and _add on the node corresponding to the key.
Solution
Step 1: Understand reuse operation
Reuse means marking a key as recently used without returning its value, so usage order must be updated.
Step 2: Efficiently update usage order
Calling _remove and _add on the node moves it to the front in O(1) time, correctly updating usage.
Final Answer:
Option D -> Option D
Quick Check:
Reuse requires usage update without value retrieval, done by repositioning node [OK]
Hint: Reuse updates usage order like get but without returning value [OK]