We're building a stock price tracker that handles a stream of records arriving out of order. Sometimes a correction comes in that overwrites a previous price at the same timestamp. At any point, we need to answer three questions: what's the most recent price, what's the highest price across all timestamps, and what's the lowest?
The current price is straightforward if we track which timestamp is the latest. The harder part is maintaining the maximum and minimum efficiently when corrections happen. If timestamp 1's price changes from 10 to 3, and 10 was the maximum, decrementing a counter is not enough. We have to find the new maximum after removing 10 from consideration.
That correction is what separates this from a plain "running max/min" problem. We need a data structure that supports both adding new prices and removing old ones when they get corrected, while still answering max and min queries quickly.
1 <= timestamp, price <= 10^9 -> Values are too large to index arrays by timestamp or price. We need hash maps and dynamic ordered structures.At most 10^5 total calls -> Each operation should run in O(log n) so the total work stays around a few million steps, well within time limits. An O(n) per-query scan would push the worst case toward 10^10 operations.Store the latest price for each timestamp in a hash map, and track which timestamp is the most recent. For current, look up the latest timestamp's price. For maximum and minimum, scan through all stored prices.
This is correct, but the scan is the bottleneck. Every max/min query walks through every timestamp seen so far, which is O(n) per query.
update(timestamp, price), set map[timestamp] = price and update the latest timestamp if this one is newer.current(), return the price at the latest timestamp.maximum(), iterate through all values in the hash map and return the largest.minimum(), iterate through all values in the hash map and return the smallest.update and current, O(n) for maximum and minimum. Each update is a hash map put and a comparison. But maximum and minimum scan every entry in the map, where n is the number of distinct timestamps.Each maximum or minimum call scans all n stored prices. The next approach maintains the extremes incrementally as prices are added and corrected, so a query no longer rescans everything.
A max-heap keeps the largest element on top and a min-heap keeps the smallest, both readable in O(1). The complication is corrections: when a timestamp's price gets corrected, the old price is still buried somewhere inside the heap, and a binary heap cannot remove an arbitrary interior element efficiently.
Lazy deletion sidesteps this. Instead of removing the stale entry when a correction happens, we leave it in the heap and push the new price as a separate entry. When we read the top, we check whether its price still matches the hash map for that timestamp. If it doesn't, the entry is stale, so we pop it and check the next one, repeating until the top is valid.
A stale entry is safe to ignore because the hash map always holds the authoritative current price for each timestamp. An entry (price, timestamp) is current only when priceMap[timestamp] == price, so the validity check never discards a live maximum or minimum.
(price, timestamp) pair.update(timestamp, price): update the hash map, push (price, timestamp) to both heaps, and update the latest timestamp.current(): return the price at the latest timestamp from the hash map.maximum(): peek at the max-heap. If the top entry's price doesn't match the hash map for that timestamp, pop it (it's stale). Repeat until the top is valid. Return the top price.minimum(): same as maximum but with the min-heap.update is O(log n) for the heap push. current is O(1). maximum/minimum is O(log n) amortized: each entry is pushed once and popped at most once, and a single push or pop costs O(log n), so the cleanup work spread across all queries averages out to O(log n) per call.update adds one entry to each heap, so over m updates the heaps hold O(m) entries, which is O(n) in the number of distinct timestamps plus corrections. Stale entries are bounded by the number of updates.The heap approach accumulates stale entries until they reach the top. The next approach removes a corrected price the moment it happens, so the structure never holds anything stale.
A sorted structure mapping each price to how many timestamps currently hold that price keeps the data consistent at all times. Adding a price increments its count. A correction decrements the old price's count, removing the key entirely when the count reaches zero, and increments the new price's count. The minimum is the smallest key and the maximum is the largest key.
A balanced BST such as Java's TreeMap supports this directly: O(log n) for insert and delete, and O(log n) to read the first key (minimum) or last key (maximum). Because corrected prices are removed immediately, the structure never carries stale entries, so a query reads an extreme without any cleanup pass.
The frequency count is what makes deletion correct when several timestamps share a price. If two timestamps both hold price 5, the key 5 has count 2, and correcting one of them drops the count to 1 rather than removing 5 outright. The key disappears only when no timestamp holds that price.
update(timestamp, price): if the timestamp already exists, get the old price and decrement its count in the sorted map (remove if count reaches zero). Set the new price in the hash map. Increment the new price's count in the sorted map. Update the latest timestamp.current(): return the price at the latest timestamp.maximum(): return the last (highest) key in the sorted map.minimum(): return the first (lowest) key in the sorted map.TreeMap, C++ map, C# SortedDictionary, Rust BTreeMap, Python SortedList), update is O(log n) for the insert and delete, current is O(1), and maximum/minimum is O(log n) to read the first or last key. The Go, JavaScript, and TypeScript versions keep prices in a sorted array: the position is found with binary search in O(log n), but inserting or deleting shifts elements, so update is O(n) in those languages while maximum/minimum stay O(1). For the constraint of up to 10^5 calls, the array versions still pass.