Topics › Stacks and Queues › Implementation Problems
Stock span problem
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given the daily prices of a stock, prices[0..n-1]. The span of day i is the number of consecutive days ending at day i (counting day i itself) on which the price was less than or equal to prices[i]. Return the span of every day.
The LeetCode version (Online Stock Span) is the same question asked online: a StockSpanner object receives one price per call to next(price) and must return that day’s span immediately, without seeing the future.
Constraints: 1 ≤ n ≤ 10^5, 1 ≤ prices[i] ≤ 10^5.
Example 1
prices = [100, 80, 60, 70, 60, 75, 85]
spans = [ 1, 1, 1, 2, 1, 4, 6]
// day 5 (75): 75, 60, 70, 60 are all <= 75, then 80 stops the run -> 4
// day 6 (85): 85, 75, 60, 70, 60, 80 are all <= 85, then 100 stops it -> 6
Example 2
prices = [10, 4, 5, 90, 120, 80]
spans = [ 1, 1, 2, 4, 5, 1]
Restating the question. Walking left from day i, the run stops at the first day with a price strictly greater than prices[i]. If that day is p (or -1 if there is none), the span is i - p. So this is the previous greater element problem in disguise.
Approach 1: Brute force
Intuition. Do what the definition says: from each day, walk left while the price is not higher, and count the steps.
Algorithm.
- For each day
i, start withj = i. - While
j ≥ 0andprices[j] ≤ prices[i], decrementj. - The span is
i - j(jis now the previous greater day, or-1).
static int[] bruteForce(int[] prices) {
int n = prices.length;
int[] span = new int[n];
for (int i = 0; i < n; i++) {
int j = i;
while (j >= 0 && prices[j] <= prices[i]) j--;
span[i] = i - j; // j is the previous greater day, or -1
}
return span;
}
Complexity. Time O(n^2) in the worst case: for an increasing price list, day i walks all the way back to day 0. Space O(1) besides the output.
Approach 2: Optimal, previous greater element with a monotonic stack
Intuition. When we walk left from day i, many days are pointless to visit. Suppose day j < i has prices[j] ≤ prices[i]. For any later day t > i, the walk from t either stops at day i or goes past it. If it goes past day i, then prices[t] ≥ prices[i] ≥ prices[j], so it also goes past day j. Either way, day j can never be the stopping point for any later day. Day i shadows it.
So keep only the days that are not shadowed. They form a stack of indices whose prices are strictly decreasing from bottom to top. For a new day i:
- Pop every index whose price is
≤ prices[i]; those days are part of dayi’s run, and from now on they are shadowed by dayi. - If the stack is now empty, no earlier day is higher: the span is
i + 1. Otherwise the top is the previous greater dayp, and the span isi - p. - Push
i.
Algorithm.
- For each day
i: pop whileprices[top] ≤ prices[i]. span[i] = i + 1if the stack is empty, elsei - top.- Push
i.
static int[] optimal(int[] prices) {
int n = prices.length;
int[] span = new int[n];
Deque<Integer> st = new ArrayDeque<>(); // indices whose prices strictly decrease from bottom to top
for (int i = 0; i < n; i++) {
while (!st.isEmpty() && prices[st.peek()] <= prices[i]) st.pop();
span[i] = st.isEmpty() ? i + 1 : i - st.peek();
st.push(i);
}
return span;
}
Complexity. Time O(n): each index is pushed once and popped at most once, so the total work of all the while loops is bounded by n. Space O(n) for the stack in the worst case (strictly decreasing prices).
Approach 3: Online, store spans on the stack
Intuition. In the online version we do not have an array of past prices to index into. We can still keep the same stack; we just store, for each surviving day, the pair {price, span} instead of an index.
Here is why the span is enough. When day i pops an entry {price, span}, that entry stands for a whole block of consecutive days: the popped day plus the span - 1 days it had absorbed, all with prices ≤ price ≤ prices[i]. So day i’s span grows by that entry’s entire span at once. Starting from 1 (day i itself) and adding the spans of everything popped gives exactly the length of the run.
Algorithm.
next(price): setspan = 1.- While the top’s price is
≤ price, pop it and add its span tospan. - Push
{price, span}and returnspan.
static class StockSpanner {
private final Deque<int[]> st = new ArrayDeque<>(); // {price, span}
int next(int price) {
int span = 1;
while (!st.isEmpty() && st.peek()[0] <= price) span += st.pop()[1];
st.push(new int[]{price, span});
return span;
}
}
static int[] online(int[] prices) {
StockSpanner spanner = new StockSpanner();
int[] span = new int[prices.length];
for (int i = 0; i < prices.length; i++) span[i] = spanner.next(prices[i]);
return span;
}
The helper online(prices) simply feeds an array through a fresh StockSpanner so the three approaches can be tested against the same expected output.
Dry run on Example 1 with the online spanner (stack shown bottom to top as price:span):
| price | popped entries | span | stack after |
|---|---|---|---|
| 100 | none | 1 | 100:1 |
| 80 | none | 1 | 100:1, 80:1 |
| 60 | none | 1 | 100:1, 80:1, 60:1 |
| 70 | 60:1 | 1 + 1 = 2 | 100:1, 80:1, 70:2 |
| 60 | none | 1 | 100:1, 80:1, 70:2, 60:1 |
| 75 | 60:1, 70:2 | 1 + 1 + 2 = 4 | 100:1, 80:1, 75:4 |
| 85 | 75:4, 80:1 | 1 + 4 + 1 = 6 | 100:1, 85:6 |
Complexity. Each call is O(1) amortised: across all calls, each price is pushed once and popped at most once. Space O(n) for the stack.
Edge cases and pitfalls
- Equal prices extend the span. The definition says “less than or equal”, so
[5, 5, 5]gives[1, 2, 3]. The stack must pop on≤, not<. Popping only on<would compute “previous greater or equal” and give[1, 1, 1]. - Increasing prices.
[1, 2, 3, 4]gives[1, 2, 3, 4]; the stack is emptied on every day, and the “empty stack meansi + 1” rule is what makes it right. - Decreasing prices. Every span is
1, and the stack grows tonentries. - First day. Its span is always
1. - Index arithmetic. With an index stack the span is
i - top, noti - top + 1: the previous greater day itself is excluded.
Related problems
- Next Greater Element is the mirror image: scan from the right instead of the left.
- Next Smaller Element uses the same stack with the comparison flipped.
- Sliding Window Maximum uses the same shadowing argument with a deque.
- Largest rectangle in a histogram needs the previous and next smaller bar for every index.