Two Heaps
Split data into two halves. Max-heap for the lower half, min-heap for the upper half. Two invariants. Median in O(1). Every "median of a stream" problem solved.
Re-sorting for each query = O(n² log n). Two heaps = O(log n) insert, O(1) median.
The insight: you don't need the full sorted order — just the middle. Keep all numbers smaller than the median in a max-heap and all numbers larger in a min-heap. The median is always at the tops of these two heaps.
The Two-Heap Structure
Visualize two heaps meeting in the middle.
heapq with negation: heappush(h, -x)standard heapq: heappush(h, x)max_heap.top ≤ min_heap.top|max_size - min_size| ≤ 1The 3-Step Insert
Three steps on every insert — always in this order.
Every new number goes to max_heap first, regardless of its value. This may temporarily violate the value invariant — that's expected and handled in step 2.
heapq.heappush(max_heap, -num)
If max_heap.top > min_heap.top, the largest 'small' number is actually larger than the smallest 'large' number — violation. Move max_heap.top to min_heap.
if min_heap and (-max_heap[0]) > min_heap[0]:
val = -heapq.heappop(max_heap)
heapq.heappush(min_heap, val)After value-balancing, check sizes. If max_heap has 2+ more elements than min_heap, or min_heap has more, rebalance by moving the top element between heaps.
if len(max_heap) > len(min_heap) + 1:
val = -heapq.heappop(max_heap)
heapq.heappush(min_heap, val)
elif len(min_heap) > len(max_heap):
val = heapq.heappop(min_heap)
heapq.heappush(max_heap, -val)Pattern Recognition
Two heaps = any problem needing the 'middle' of a dynamic dataset.