How many days in a row the price was no higher
You're given the daily prices of a stock in prices, and the span on day i is
how many days in a row (ending on day i and counting it) the price was at most prices[i].
You'd return the span for every day.
Examples
| Input | Output | Why |
|---|---|---|
prices = [100, 80, 60, 70, 60, 75, 85] | [1, 1, 1, 2, 1, 4, 6] | On the sixth day the 75 reaches back over 60 and 70 and 60 until it gets to the 80 (which is higher) so you'd say its span is 4. |
prices = [1, 2, 3, 4, 5] | [1, 2, 3, 4, 5] | Every day's the highest you've seen so far and so each span goes all the way back to the start. |
prices = [3, 3, 3, 3] | [1, 2, 3, 4] | I'd point out that equal prices count here since the rule says at most. |
Constraints
- 1 ≤ n ≤ 10⁵
- 1 ≤ prices[i] ≤ 10⁵
Hints
Hint 1
If you walk back from every day it's O(n²) when the prices go up steadily, so I'd ask which of the earlier days could ever stop a span later on.
Hint 2
It's only a day that's higher than everything that's come after it so far, because once a higher or equal day turns up the earlier one's hidden behind it for good.
Hint 3
So you'd keep a stack of the days that are still visible (with prices going down from the bottom to the top) and for each new day you pop every day that's priced at most today's. Whatever's left on top (or the start, if nothing is) is where the span stops.
Where people lose marks
- Popping only the prices that are strictly below today's, which leaves an equal earlier day on the stack and stops the span a day too early.
- Putting prices on the stack where you'd want the indices, when it's the index of the higher day that tells you how far back the span goes.
- Worrying that the while loop inside the for loop makes it quadratic. I wouldn't, because each index can only leave the stack once and so all the pops together come to n at most.
Dry run on [100,80,60,70,60,75,85]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 8prices1000801602703604755856span·0·1·2·3·4·5·6stack[]
I'd keep a stack of the days that are still visible, and you'll see that their prices always go down from the bottom to the top.
-
Step 2 / 8prices1000801602703604755856span10·1·2·3·4·5·6stack[0]
Nothing gets popped for 100 on day 0, and there's nothing earlier that's higher so the span goes right back to the start and it's 1.
-
Step 3 / 8prices1000801602703604755856span1011·2·3·4·5·6stack[0, 1]
Day 1 at 80 doesn't hide anything, and the nearest higher day is day 0 at 100 so the span is 1 − 0 = 1.
-
Step 4 / 8prices1000801602703604755856span101112·3·4·5·6stack[0, 1, 2]
Nothing gets popped for 60 on day 2, and the nearest higher day is day 1 at 80 so the span is 2 − 1 = 1.
-
Step 5 / 8prices1000801602703604755856span10111223·4·5·6stack[0, 1, 3]
Day 3 at 70 hides day 2 so it comes off the stack, and the nearest higher day is day 1 at 80 so the span is 3 − 1 = 2.
-
Step 6 / 8prices1000801602703604755856span1011122314·5·6stack[0, 1, 3, 4]
Nothing gets popped for 60 on day 4, and the nearest higher day is day 3 at 70 so the span is 4 − 3 = 1.
-
Step 7 / 8prices1000801602703604755856span101112231445·6stack[0, 1, 5]
Day 5 at 75 hides days 3 and 4 so they come off the stack, and the nearest higher day is day 1 at 80 so the span is 5 − 1 = 4.
-
Step 8 / 8prices1000801602703604755856span10111223144566stack[0, 6]
Day 6 at 85 hides days 1 and 5 so they come off the stack, and the nearest higher day is day 0 at 100 so the span is 6 − 0 = 6.
How to think about it
The obvious way is to walk back from each day until you find a higher price, and if the prices climb steadily you'll end up taking 1 + 2 + … + n steps. What saves you is that most of the earlier days can't ever matter again. Once a day priced at least p turns up, any earlier day priced at most p is hidden behind it, because a span that got past the new day would get past the old one as well.
So I'd keep a stack of the indices that are still visible, and their prices go down as you go from the bottom of
the stack to the top. When a new day comes in you pop every index whose price is at most today's, and whatever's left
on top is the nearest earlier day that's higher. That makes the span i − top (or i + 1 if
the stack's empty) and then today goes on the stack too.
Every index gets pushed once and popped at most once, so it's O(n) overall even though there's a loop inside the loop.
Complexity. Time O(n) · Space O(n)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def stock_span(prices):
st, out = [], []
for i, p in enumerate(prices):
while st and prices[st[-1]] <= p: # days hidden behind today
st.pop()
out.append(i - st[-1] if st else i + 1)
st.append(i)
return out#include <vector>
std::vector<int> solve(const std::vector<int>& prices) {
std::vector<int> st, out;
for (int i = 0; i < (int)prices.size(); ++i) {
while (!st.empty() && prices[st.back()] <= prices[i]) st.pop_back(); // days hidden behind today
out.push_back(st.empty() ? i + 1 : i - st.back());
st.push_back(i);
}
return out;
}class Solution {
public int[] stockSpan(int[] prices) {
int n = prices.length, top = -1;
int[] st = new int[n], out = new int[n];
for (int i = 0; i < n; i++) {
while (top >= 0 && prices[st[top]] <= prices[i]) top--; // days hidden behind today
out[i] = top >= 0 ? i - st[top] : i + 1;
st[++top] = i;
}
return out;
}
}function stockSpan(prices) {
const st = [], out = [];
prices.forEach((p, i) => {
while (st.length && prices[st[st.length - 1]] <= p) st.pop(); // days hidden behind today
out.push(st.length ? i - st[st.length - 1] : i + 1);
st.push(i);
});
return out;
}fn solve(prices: &[i64]) -> Vec<i64> {
let mut st: Vec<usize> = Vec::new();
let mut out = Vec::with_capacity(prices.len());
for (i, &p) in prices.iter().enumerate() {
while let Some(&t) = st.last() {
if prices[t] <= p { st.pop(); } else { break; } // days hidden behind today
}
out.push(match st.last() { Some(&t) => (i - t) as i64, None => i as i64 + 1 });
st.push(i);
}
out
}-- The stack holds (index, price) pairs, top at the head.
solve :: [Int] -> [Int]
solve prices = go [] (zip [0 ..] prices)
where
go _ [] = []
go st ((i, p) : rest) =
let st' = dropWhile (\(_, q) -> q <= p) st -- days hidden behind today
s = case st' of
((j, _) : _) -> i - j
[] -> i + 1
in s : go ((i, p) : st') rest
Variants to try
- If it's an online class that gets one price at a time you'd keep (price, span) pairs on the stack and add up the spans of whatever you pop.
- If you're asked for the next higher price to the right it's the same stack run from the other end, and that's really the warmer-day problem.
Next problems
Days until a warmer day
For each day, how long until it is warmer? Days wait on a stack until a warmer one arrives and settles them.
Open the problem → Medium20 minThe sum of the minimum of every subarray
Turn it round: count, for each value, how many subarrays it's the minimum of. Two monotonic stacks give how far it reaches each way.
Open the problem → Hard25 minLargest rectangle in a bar chart
Each bar is the height of some rectangle. A stack finds, for every bar at once, how far it can stretch.
Open the problem →Write a solution and run it against the real test table.
Keep going
Days until a warmer day
For each day, how long until it is warmer? Days wait on a stack until a warmer one arrives and settles them.
Open the problem → Medium20 minThe sum of the minimum of every subarray
Turn it round: count, for each value, how many subarrays it's the minimum of. Two monotonic stacks give how far it reaches each way.
Open the problem → Hard25 minLargest rectangle in a bar chart
Each bar is the height of some rectangle. A stack finds, for every bar at once, how far it can stretch.
Open the problem → StackThe pattern behind it
Keep the elements whose question is still open; each new element settles the ones it beats and waits its turn.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.