Bird
Raised Fist0
Interview Prepchallenge-problemshardAmazonFacebookGoogleMicrosoft

Find Median from Data Stream

Choose your preparation mode4 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
📋
Problem

Imagine you are monitoring live sensor data or stock prices and need to report the median value at any moment as new data arrives continuously.

Design a data structure that supports adding numbers from a data stream and finding the median of all elements so far. Implement two methods: addNum(int num) - adds a number to the data structure; findMedian() - returns the median of all elements added so far. The median is the middle value in an ordered integer list. If the size of the list is even, the median is the average of the two middle numbers.

1 ≤ number of addNum calls ≤ 10^5-10^5 ≤ num ≤ 10^5At least one element will be present before calling findMedian
Edge cases: Only one element added → median is that elementAll elements are the same → median is that elementLarge number of elements with duplicates → median calculation remains correct
</>
IDE
class MedianFinder: def __init__(self): pass def addNum(self, num: int) -> None: pass def findMedian(self) -> float: passpublic class MedianFinder { public MedianFinder() {} public void addNum(int num) {} public double findMedian() { return 0; } }class MedianFinder { public: MedianFinder() {} void addNum(int num) {} double findMedian() { return 0; } };class MedianFinder { constructor() {} addNum(num) {} findMedian() { return 0; } }
class MedianFinder:
    def __init__(self):
        # Write your solution here
        pass
    def addNum(self, num: int) -> None:
        pass
    def findMedian(self) -> float:
        pass
public class MedianFinder {
    public MedianFinder() {
        // Write your solution here
    }
    public void addNum(int num) {
    }
    public double findMedian() {
        return 0;
    }
}
#include <vector>
using namespace std;

class MedianFinder {
public:
    MedianFinder() {
        // Write your solution here
    }
    void addNum(int num) {
    }
    double findMedian() {
        return 0;
    }
};
class MedianFinder {
    constructor() {
        // Write your solution here
    }
    addNum(num) {
    }
    findMedian() {
        return 0;
    }
}
Coming soon
0/9
Common Bugs to Avoid
Wrong: Median values off by one or incorrect averagesNot balancing the two heaps properly after insertionAfter each addNum, rebalance heaps so their sizes differ by at most one
Wrong: Median is always the last inserted element or first elementUsing a greedy approach that does not maintain order or balanceUse two heaps to maintain lower and upper halves and compute median from heap tops
Wrong: Median incorrect when duplicates are presentIgnoring duplicates or treating them as single elementsInsert all duplicates into heaps and balance normally
Wrong: TLE on large inputsSorting entire list on every addNum callImplement two heaps approach for O(log n) insertion and O(1) median retrieval
Wrong: Median calculation causes integer overflow or wrong averagingUsing integer division or not using float/double for medianUse float/double type and compute average as (a + b) / 2.0
Test Cases
t1_01basic
Input["addNum(1)","addNum(2)","findMedian()","addNum(3)","findMedian()"]
Expected[1.5,2]

After adding 1 and 2, the sorted list is [1,2], median is (1+2)/2=1.5. After adding 3, list is [1,2,3], median is 2.

t1_02basic
Input["addNum(5)","addNum(15)","addNum(1)","findMedian()","addNum(3)","findMedian()"]
Expected[5,4]

After adding 5,15,1 the sorted list is [1,5,15], median is 5. After adding 3, list is [1,3,5,15], median is (3+5)/2=4.

t2_01edge
Input["addNum(10)","findMedian()"]
Expected[10]

Only one element added, median is that element itself.

t2_02edge
Input["addNum(7)","addNum(7)","addNum(7)","findMedian()"]
Expected[7]

All elements are the same, median is that element.

t2_03edge
Input["addNum(-100000)","addNum(100000)","findMedian()"]
Expected[0]

Elements at boundary values, median is average of -100000 and 100000 = 0.

t3_01corner
Input["addNum(1)","addNum(2)","addNum(3)","addNum(4)","addNum(5)","findMedian()"]
Expected[3]

Sequential increasing numbers, median is middle element 3.

t3_02corner
Input["addNum(1)","addNum(1)","addNum(1)","addNum(1)","findMedian()"]
Expected[1]

Repeated identical elements, test for confusion between 0/1 knapsack style counting and median calculation.

t3_03corner
Input["addNum(100000)","addNum(-100000)","addNum(0)","addNum(99999)","addNum(-99999)","findMedian()"]
Expected[0]

Mixed large positive and negative numbers, median is 0 after sorting [-100000,-99999,0,99999,100000].

t4_01performance
Input["addNum(0)","addNum(1)","addNum(2)","addNum(3)","addNum(4)","addNum(5)","addNum(6)","addNum(7)","addNum(8)","addNum(9)","addNum(10)","addNum(11)","addNum(12)","addNum(13)","addNum(14)","addNum(15)","addNum(16)","addNum(17)","addNum(18)","addNum(19)","addNum(20)","addNum(21)","addNum(22)","addNum(23)","addNum(24)","addNum(25)","addNum(26)","addNum(27)","addNum(28)","addNum(29)","addNum(30)","addNum(31)","addNum(32)","addNum(33)","addNum(34)","addNum(35)","addNum(36)","addNum(37)","addNum(38)","addNum(39)","addNum(40)","addNum(41)","addNum(42)","addNum(43)","addNum(44)","addNum(45)","addNum(46)","addNum(47)","addNum(48)","addNum(49)","addNum(50)","addNum(51)","addNum(52)","addNum(53)","addNum(54)","addNum(55)","addNum(56)","addNum(57)","addNum(58)","addNum(59)","addNum(60)","addNum(61)","addNum(62)","addNum(63)","addNum(64)","addNum(65)","addNum(66)","addNum(67)","addNum(68)","addNum(69)","addNum(70)","addNum(71)","addNum(72)","addNum(73)","addNum(74)","addNum(75)","addNum(76)","addNum(77)","addNum(78)","addNum(79)","addNum(80)","addNum(81)","addNum(82)","addNum(83)","addNum(84)","addNum(85)","addNum(86)","addNum(87)","addNum(88)","addNum(89)","addNum(90)","addNum(91)","addNum(92)","addNum(93)","addNum(94)","addNum(95)","addNum(96)","addNum(97)","addNum(98)","addNum(99)","findMedian()"]
⏱ Performance - must finish in 2000ms

n=100 addNum calls followed by findMedian; O(log n) per addNum and O(1) findMedian must complete within 2 seconds.

Practice

(1/5)
1. You need to design a cache that evicts the least frequently used item when full, and among items with the same frequency, evicts the least recently used one. Which approach guarantees O(1) average time complexity for both get and put operations?
easy
A. Using a simple hash map with frequency counts and sorting keys on each eviction
B. Maintaining a frequency map of doubly linked lists to track keys by frequency and recency
C. Using a min-heap keyed by frequency and timestamp to evict the least frequently used item
D. Using a single doubly linked list ordered by frequency and recency

Solution

  1. Step 1: Understand the eviction criteria

    The cache must evict the least frequently used key, and if multiple keys share the same frequency, evict the least recently used among them.
  2. Step 2: Identify data structures supporting O(1) operations

    A frequency map with doubly linked lists allows grouping keys by frequency and maintaining recency order within each frequency group, enabling O(1) updates and eviction.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Frequency map + doubly linked lists is the classic LFU O(1) approach [OK]
Hint: Frequency map + doubly linked lists enable O(1) LFU cache [OK]
Common Mistakes:
  • Using sorting on eviction is too slow
  • Min-heap adds O(log n) overhead
  • Single list can't maintain frequency and recency efficiently
2. Given the following addRange method snippet from the optimal RangeModule implementation, what is the value of self.starts after calling addRange(2, 6) on an initially empty RangeModule?
def addRange(self, left: int, right: int) -> None:
    i = bisect_left(self.starts, left)
    j = bisect_right(self.starts, right)

    if i != 0 and self.intervals[self.starts[i-1]] >= left:
        i -= 1
        left = min(left, self.starts[i])
    if j != 0:
        right = max(right, self.intervals[self.starts[j-1]])

    for k in self.starts[i:j]:
        del self.intervals[k]
    self.starts[i:j] = [left]
    self.intervals[left] = right
easy
A. [2, 6, 7]
B. [2, 6]
C. [2]
D. [6]

Solution

  1. Step 1: Trace bisect indices on empty starts list

    Initially, self.starts = []. Calling bisect_left([], 2) returns 0, and bisect_right([], 6) returns 0.
  2. Step 2: Update intervals and starts

    Since i == 0, the first if condition is skipped. Also, j == 0, so second if is skipped. No intervals to delete. Insert left=2 at self.starts[0:0], so self.starts = [2]. Interval stored as {2:6}.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Starts list after first addRange is [2] [OK]
Hint: Bisect on empty list returns 0, so starts list gets single element [OK]
Common Mistakes:
  • Assuming right endpoint is also inserted in starts list
3. What is the space complexity of the optimal approach that copies a linked list with random pointers by interleaving copied nodes within the original list?
medium
A. O(1) because no extra data structures proportional to n are used
B. O(n) due to storing all copied nodes in a hash map
C. O(n) due to recursion stack in the copying process
D. O(n) because of auxiliary arrays used to track random pointers

Solution

  1. Step 1: Identify auxiliary data structures

    The optimal approach does not use hash maps or arrays; it weaves copied nodes into the original list.
  2. Step 2: Analyze space usage

    Only a few pointers are used; no extra space proportional to n is allocated, so space complexity is O(1).
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    No hash maps or recursion stacks used [OK]
Hint: Interleaving nodes avoids extra space [OK]
Common Mistakes:
  • Assuming hash map is always needed, so O(n) space
  • Confusing recursion stack space with iterative approach
  • Thinking auxiliary arrays are used for random pointers
4. Consider the following buggy implementation of SnapshotArray. Which line contains the subtle bug that can cause incorrect get results when multiple sets occur before a snap on the same index?
import bisect

class SnapshotArray:
    def __init__(self, length: int):
        self.snap_id = 0
        self.data = [[(-1, 0)] for _ in range(length)]

    def set(self, index: int, val: int) -> None:
        # Bug here
        self.data[index].append((self.snap_id, val))

    def snap(self) -> int:
        self.snap_id += 1
        return self.snap_id - 1

    def get(self, index: int, snap_id: int) -> int:
        arr = self.data[index]
        i = bisect.bisect_right(arr, (snap_id, float('inf'))) - 1
        return arr[i][1]
medium
A. Line in snap(): incrementing snap_id before returning
B. Line in __init__(): initializing with (-1, 0) instead of (0, 0)
C. Line in set(): always appending without checking if last snap_id matches
D. Line in get(): using bisect_right instead of bisect_left

Solution

  1. Step 1: Analyze set() method

    The set method always appends (snap_id, val) without checking if the last entry has the same snap_id, causing duplicate entries for the same snap_id.
  2. Step 2: Consequence on get()

    Duplicate entries for the same snap_id break binary search assumptions and can cause get to return incorrect values.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Correct code replaces last entry if snap_id matches [OK]
Hint: Check if last snap_id matches before appending in set() [OK]
Common Mistakes:
  • Appending blindly causes duplicates
  • Misunderstanding initialization defaults
5. Suppose the RangeModule must support intervals with negative coordinates and allow intervals to be added and removed multiple times (reusing intervals). Which modification to the optimal balanced BST approach is necessary to handle this correctly?
hard
A. Replace balanced BST with a segment tree to handle negative and overlapping intervals efficiently.
B. Use a hash map keyed by interval length instead of start points to speed up queries.
C. No modification needed; the existing balanced BST approach handles negative and reused intervals naturally.
D. Store intervals as half-open [start, end) and adjust bisect searches to handle negative values correctly.

Solution

  1. Step 1: Consider negative coordinates impact

    Bisect operations and interval storage must correctly handle negative values, which requires no change in data structure but careful handling of interval boundaries.
  2. Step 2: Consider interval reuse

    Intervals can be added and removed multiple times, so storing intervals as half-open [start, end) and merging intervals correctly remains essential.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Adjust bisect and interval representation for negatives and reuse [OK]
Hint: Half-open intervals and bisect handle negatives and reuse correctly [OK]
Common Mistakes:
  • Thinking data structure must change to segment tree unnecessarily