See how a monotonic decreasing stack efficiently calculates the span of stock prices — consecutive days where price was less than or equal to today's.
Monotonic Stack LeetCode 901 ↗ Medium[100, 80, 60, 70, 60, 75, 85], the spans are [1, 1, 1, 2, 1, 4, 6].
On day 6 (price 85), the span is 6 because prices on days 1–5 (80, 60, 70, 60, 75) are all ≤ 85, and day 0 (100) is not.(price, span) pairs lets us
absorb the spans of all popped entries, giving O(n) amortized time.
fun stockSpan(prices: IntArray): IntArray { val stack = ArrayDeque<Pair<Int, Int>>() // (price, span) val result = mutableListOf<Int>() for (price in prices) { var span = 1 while (stack.isNotEmpty() && stack.last().first <= price) span += stack.removeLast().second stack.addLast(Pair(price, span)) result.add(span) } return result.toIntArray()}
The stack stores (price, span) pairs in strictly decreasing order of price.
When a new price arrives that is greater than or equal to the stack top, we pop entries and absorb
their spans into the current span. This works because if today's price dominates a previous entry, it also
dominates everything that entry already dominated.
span = 1 for each new price (it always counts itself).(topPrice, topSpan) and add topSpan to the current span. The popped entry's entire span is absorbed.(price, span) onto the stack. The stack remains in decreasing order because the current price is strictly less than whatever is now on top (or the stack is empty).
After each push, the stack prices are strictly decreasing from bottom to top. A new price that is
large enough pops all smaller-or-equal entries, absorbing their spans. This is what makes the stack
"monotonic decreasing." Each element is pushed and popped at most once, giving amortized O(1) per
next() call.
Each price is pushed onto the stack exactly once and popped at most once across all next() calls.
So the total work across n calls is O(n), giving O(1) amortized per call.
The stack can hold at most n elements (when prices are strictly decreasing), so space is O(n).