086. Kth Largest Stream Class
Problem
Implement KthLargest(k, initial_values) with positive k. add(value) inserts one integer and returns the kth largest value including duplicates, or None until at least k values have been seen. Keep at most k values internally.
Examples
Example 1
run_operations(KthLargest, [("add",3),("add",5),("add",10),("add",9),("add",4)], 3, [4,5,8,2])
=> [4, 5, 5, 8, 8]Each result is the weakest member of the three-value winner pool.
Example 2
run_operations(KthLargest, [("add",1),("add",2)], 3, [])
=> [None, None]The statistic is undefined before the third observation.
Example 3
run_operations(KthLargest, [("add",2),("add",1)], 1, [])
=> [2, 2]The first largest is the running maximum.
Reveal the worked solution and reasoning
Why this mechanism works
The root of a min-heap containing the largest k values is the kth largest value itself. Unlike the previous exercise, there is no need to sort winners because only their weakest member is queried. Constructor inputs go through the same insertion transition as later arrivals.
State and boundaries
heap contains the largest at-most-k values observed. Its root is meaningful as kth largest only when its size reaches k.
Protocol
- Initialize an empty heap and insert initial values.
- For add, push the new value.
- Evict the minimum if size exceeds k.
- Return the root if size is k, else None.
Visualization
k=3, winners [4,5,8] -> kth=4
add 3 -> keep [4,5,8] -> 4
add 5 -> keep [5,5,8] -> 5Python implementation
import heapq
from structures import run_operations
class KthLargest:
def __init__(self, k, values):
if k < 1:
raise ValueError('k must be positive')
self.k, self.heap = k, []
for value in values:
self.add(value)
def add(self, value):
heapq.heappush(self.heap, value)
if len(self.heap) > self.k:
heapq.heappop(self.heap)
return self.heap[0] if len(self.heap) == self.k else None
Worked execution
run_operations(KthLargest, [("add",3),("add",5),("add",10),("add",9),("add",4)], 3, [4,5,8,2])
L6 __init__: if k < 1:
self=KthLargest{}; k=3; values=[4, 5, 8, 2]
L8 __init__: self.k, self.heap = k, []
self=KthLargest{'k':3, 'heap':[]}; k=3; values=[4, 5, 8, 2]
L9 __init__: for value in values:
self=KthLargest{'k':3, 'heap':[]}; k=3; values=[4, 5, 8, 2]; value=4
L13 add: heapq.heappush(self.heap, value)
self=KthLargest{'k':3, 'heap':[...]}; value=4
L10 __init__: self.add(value)
self=KthLargest{'k':3, 'heap':[...]}; k=3; values=[4, 5, 8, 2]; value=4
L9 __init__: for value in values:
self=KthLargest{'k':3, 'heap':[...]}; k=3; values=[4, 5, 8, 2]; value=5
L13 add: heapq.heappush(self.heap, value)
self=KthLargest{'k':3, 'heap':[..., ...]}; value=5
L10 __init__: self.add(value)
self=KthLargest{'k':3, 'heap':[..., ...]}; k=3; values=[4, 5, 8, 2]; value=5
... 12 intermediate state changes omitted ...
L13 add: heapq.heappush(self.heap, value)
self=KthLargest{'k':3, 'heap':[..., ..., ..., ...]}; value=9
L15 add: heapq.heappop(self.heap)
self=KthLargest{'k':3, 'heap':[..., ..., ...]}; value=9
L13 add: heapq.heappush(self.heap, value)
self=KthLargest{'k':3, 'heap':[..., ..., ..., ...]}; value=4
L15 add: heapq.heappop(self.heap)
self=KthLargest{'k':3, 'heap':[..., ..., ...]}; value=4Result: [4, 5, 5, 8, 8]. Each result is the weakest member of the three-value winner pool.
run_operations(KthLargest, [("add",1),("add",2)], 3, [])
L6 __init__: if k < 1:
self=KthLargest{}; k=3; values=[]
L8 __init__: self.k, self.heap = k, []
self=KthLargest{'k':3, 'heap':[]}; k=3; values=[]
L13 add: heapq.heappush(self.heap, value)
self=KthLargest{'k':3, 'heap':[...]}; value=1
L13 add: heapq.heappush(self.heap, value)
self=KthLargest{'k':3, 'heap':[..., ...]}; value=2Result: [None, None]. The statistic is undefined before the third observation.
Why the solution is correct
The bounded winner-pool invariant follows from removing the minimum of k+1 candidates. With k retained values, exactly k-1 retained values are at least as large as the minimum, making that minimum the kth order statistic with multiplicity. Fewer values cannot define it.
Time and space
O(log(k+1)) time per add and O(k) space; constructor cost O(m log(k+1)) for m initial values.
Common mistakes
Returning a root before k observations gives the minimum-so-far rather than a defined kth largest. Duplicate values count separately.
Reasoning card
State: heap contains the largest at-most-k values observed. Its root is meaningful as kth largest only when its size reaches k.
Invariant: Heap root is the kth largest among all values seen.
Decision to rehearse: Maintain a min-heap of exactly the best k values.
Finish: Return the root if size is k, else None
Transfer exercise
Allow deletion of old values from a sliding stream. Explain why a plain bounded heap no longer contains enough history.