Monotonic Stack · Medium
LeetCode 901: Online Stock Span — solution
Monotonic stack of (price, span) pairs: each new price absorbs the spans of all smaller-or-equal prices below it. Amortized O(1) per call and O(n) space.
Problem in plain words
Design a StockSpanner that receives one daily price at a time through next(price). Each call must return today's span: the number of consecutive days, ending today and counting backwards, on which the price was less than or equal to today's price. Today always counts, so the span is at least 1.
For example, after prices 10, 20, 15, a new price 18 has span 2 (15 and 18), because 20 breaks the streak.
Intuition
If yesterday's price is not higher than today's, then today's span swallows yesterday's entire span too, since every day inside it was also not higher. So we never need those swallowed days again; one entry holding the combined span replaces them. Keep a stack of (price, span) pairs with strictly decreasing prices. A new price pops every pair with a price less than or equal to it, adds their spans to its own, and pushes the merged result.
Approach
- The constructor creates an empty stack of pairs.
- In
next(price), start withspan = 1for today. - While the stack is not empty and the top price is
<= price, pop it and add its span tospan. - Push
(price, span)and returnspan.
Dry run
Calls: next(30), next(25), next(28), next(40), next(35), next(35).
| Price | Popped | Span | Stack after (price, span) |
|---|---|---|---|
| 30 | - | 1 | (30,1) |
| 25 | - | 1 | (30,1) (25,1) |
| 28 | (25,1) | 2 | (30,1) (28,2) |
| 40 | (28,2) (30,1) | 4 | (40,4) |
| 35 | - | 1 | (40,4) (35,1) |
| 35 | (35,1) | 2 | (40,4) (35,2) |
Java solution
class StockSpanner {
// Each entry is {price, span}; prices strictly decrease from bottom to top.
private final Deque<int[]> stack;
public StockSpanner() {
stack = new ArrayDeque<>();
}
public int next(int price) {
int span = 1;
while (!stack.isEmpty() && stack.peek()[0] <= price) {
span += stack.pop()[1];
}
stack.push(new int[] {price, span});
return span;
}
}Python solution
class StockSpanner:
def __init__(self):
# Each entry is (price, span); prices strictly decrease bottom to top.
self.stack = []
def next(self, price: int) -> int:
span = 1
while self.stack and self.stack[-1][0] <= price:
span += self.stack.pop()[1]
self.stack.append((price, span))
return spanComplexity
| Measure | Cost | Why |
|---|---|---|
| Time | Amortized O(1) per call | Each price is pushed once and popped at most once over all calls. |
| Space | O(n) | A strictly falling price series keeps every entry on the stack. |
Edge cases and common mistakes
- Equal prices: the span counts days with price less than or equal, so pop on
<=. Using<breaks the streak at a repeated price. - Forgetting today: the span starts at 1, not 0.
- Storing every price and scanning back: that is O(n) per call and O(n^2) overall on a long rising series, while the stack keeps the total linear.
- Storing only prices: without the merged span you cannot know how many days a popped entry represented. Store
(price, span)or store indices instead.
›Can I store indices instead of spans?
Yes. Keep a running day counter and a stack of (price, day). After popping, the span is today - day_of_new_top, or today + 1 if the stack is empty. Both versions are equivalent.
