Design a Structure That Returns the Max of the Last k Inserted Numbers
AmazonSoftware Engineer · OnsiteLast updated
A data structure design question: support inserting numbers into a stream and returning the maximum of the most recent k values. It covers a basic version with complexity analysis, reducing memory to O(k), comparing arrays, hash maps, linked lists and heaps, and building the optimal solution with a monotonic deque.
Design a data structure that receives a stream of numbers and reports the maximum of the most recent k numbers inserted. Implement this interface:
class SlidingWindow:
def __init__(self, k: int) -> None: ...
def put(self, number: int) -> None: ...
def getMax(self) -> int: ...
SlidingWindow(k)creates an empty structure for a window of sizek.put(number)inserts the next number of the stream.getMax()returns the largest of thekmost recently inserted numbers.
For example, with k = 3, after put(4), put(2), put(12) and put(3), getMax() returns 12, because the last three numbers are 2, 12 and 3. After a further put(1) and put(5), it returns 5, because 12 has left the window.
Clarifying Questions
- Before
knumbers have been inserted, shouldgetMaxreturn the maximum of everything inserted so far? - What should
getMaxdo when nothing has been inserted yet: raise an error or return a sentinel? - Can numbers be negative or repeated? Is
kfixed for the life of the object, and how large can it be? - Roughly how often is
getMaxcalled compared withput?
Part 1 — A basic version
Implement a straightforward working version and analyze its time and space complexity.
What This Part Should Cover
- A correct implementation, including the cases with fewer than
knumbers and with no numbers - Time per
putand pergetMax - Space as a function of the number of puts
Part 2 — Reduce space to O(k)
How do you avoid storing the whole history, so that the structure uses only O(k) space?
What This Part Should Cover
- Storage that stops growing with the number of puts
- How the oldest number is evicted on each put
- The effect of the change on the time complexity
Part 3 — Compare data structures
Compare arrays, hash maps, linked lists (including doubly linked lists) and heaps as the basis for this structure: the time and space cost of put and getMax, and the trade-offs between them.
What This Part Should Cover
- Time for
put, time forgetMax, and space for each structure - Which structures find the maximum quickly and which evict the oldest number quickly, and why few do both
- When each choice is reasonable
Part 4 — The monotonic deque
Explain how a monotonic deque gives the optimal solution, implement it, and analyze its complexity.
What This Part Should Cover
- The invariant the deque maintains and why its front is the maximum
- How numbers that have left the window are detected and removed
- Amortized versus worst-case cost per operation, and the space bound
Hints
Hint 1
Start simple: Begin with the most direct correct version, then state each cost in terms of both `k` and `n`, the total number of puts.
Hint 2
Only the last k matter: Numbers older than the window can never be returned again. Think about storage of a fixed size that new numbers overwrite.
Hint 3
Three costs per structure: For each structure, ask separately what it costs to add the newest number, to drop the oldest number and to find the largest.
Hint 4
Some numbers can never win: When a new number arrives, some older numbers in the window can never be the maximum again. Decide what to do with them at that moment.
Follow-up questions
- Also support `getMin()` in O(1). What do you add?
- The window becomes time-based: the maximum of the numbers inserted in the last 60 seconds. What changes?
- Many threads call `put` and `getMax` concurrently. How do you keep the structure correct, and what does it cost?
- What if you also need the median of the window?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.