Design a data structure that tracks the k-th largest element in a continuously growing collection of numbers. Your structure should support two operations:
The challenge is handling potentially thousands of additions efficiently—resorting the entire collection after each insertion would be too slow.
Implement a class RollingMaxTracker with:
k (integer) and nums (array of integers)add(val) method that inserts val and returns the k-th largest elementThe k-th largest element means the k-th element in descending sorted order (with 1-indexing)
If there are fewer than k elements when add is called, you may return negative infinity or handle it appropriately
Handle duplicate values correctly—they count as separate elements
addExample 1:
tracker = RollingMaxTracker(3, [4, 5, 8, 2]) tracker.add(3) // returns 4 (3rd largest among [4,5,8,2,3] is 4) tracker.add(5) // returns 5 (3rd largest among [4,5,8,2,3,5] is 5) tracker.add(10) // returns 8 (3rd largest among [4,5,8,2,3,5,10] is 8)
Example 2:
tracker = RollingMaxTracker(1, []) tracker.add(7) // returns 7 (1st largest is always the maximum) tracker.add(3) // returns 7 tracker.add(9) // returns 9
Example 3:
tracker = RollingMaxTracker(2, [0]) tracker.add(-1) // returns -1 (2nd largest among [0, -1] is -1) tracker.add(1) // returns 0 (2nd largest among [0, -1, 1] is 0) tracker.add(0) // returns 0 (2nd largest among [0, -1, 1, 0] is 0)
Hint 1: Optimal Data Structure Think about data structures that efficiently maintain sorted order or find the k-th element. You don't need to track ALL elements in sorted order—only enough to know the k-th largest. What structure lets you quickly find and remove the smallest element?
Hint 2: Size Matters If you only need the k-th largest element, do you need to keep more than k elements? Consider maintaining exactly k elements representing the k largest values seen so far. What happens when a new value is smaller than all k elements versus when it's larger?
Hint 3: Heap Strategy A min-heap of size k is perfect for this problem. The root (minimum) of the heap is always your k-th largest element. When adding a value: if it's larger than the root, remove the root and insert the new value. This keeps the heap at size k with the k largest elements, and the smallest of those k is your answer.
Full Solution `` Explanation:
The key insight is that to find the k-th largest element, we only need to track the k largest elements we've seen so far. We use a min-heap of size k because:
Min-heap property: The smallest element is at the root, accessible in O(1)
Size constraint: By keeping exactly k elements, the smallest in our heap IS the k-th largest overall
Efficient updates: When a new value arrives:
- If we have fewer than k elements, just add it
- If we have k elements and the new value is larger than the root, the root is no longer in the top k, so we remove it and add the new value
- The heap automatically reorganizes to maintain the min-heap property
Time Complexity:
- Initialization: O(n log k) where n is the initial array size
- Each
addoperation: O(log k) for heap push/pop- Overall for m additions: O((n + m) log k)
Space Complexity: O(k) - we only store k elements in the heap
This is much more efficient than sorting the entire array after each addition, which would be O(n log n) per operation where n grows with each addition.
import heapq
class RollingMaxTracker:
def __init__(self, k: int, nums: list[int]):
"""
Initialize the tracker with target position k and initial numbers.
Strategy: Maintain a min-heap of size k containing the k largest elements.
The root of this heap is the k-th largest element.
Time: O(n log k) where n is len(nums)
Space: O(k)
"""
self.k = k
self.min_heap = []
# Add all initial numbers using our add method
# This efficiently builds a heap of size k
for num in nums:
self.add(num)
def add(self, val: int) -> int:
"""
Add a value and return the current k-th largest element.
Time: O(log k) - heap operations are logarithmic in heap size
Space: O(1) - only store at most k elements total
"""
# Always add the value to the heap
heapq.heappush(self.min_heap, val)
# If heap exceeds size k, remove the smallest element
# This maintains the invariant: heap contains exactly k largest elements
if len(self.min_heap) > self.k:
heapq.heappop(self.min_heap)
# The root of the min-heap is the smallest among the k largest
# which is exactly the k-th largest overall
return self.min_heap[0]