Facebook Pixel

901. Online Stock Span

Problem Description

You need to design a class that tracks daily stock prices and calculates the "span" for each day's price.

The span of a stock price on a given day is defined as the maximum number of consecutive days (including the current day and going backward) where the stock price was less than or equal to the current day's price.

Let's understand with examples:

  • If the last 4 days had prices [7, 2, 1, 2] and today's price is 2, the span is 4. This is because looking backward from today: 2 <= 2 (today), 1 <= 2 (1 day ago), 2 <= 2 (2 days ago), 2 <= 2 (3 days ago). All 4 consecutive days have prices ≤ 2.

  • If the last 4 days had prices [7, 34, 1, 2] and today's price is 8, the span is 3. Looking backward: 8 <= 8 (today), 2 <= 8 (1 day ago), 1 <= 8 (2 days ago), but 34 > 8 (3 days ago). So only 3 consecutive days have prices ≤ 8.

You need to implement the StockSpanner class with:

  • StockSpanner(): Initializes the object
  • next(int price): Takes today's price and returns the span for that price

The key insight is that this problem can be solved efficiently using a monotonic stack. The stack maintains pairs of (price, span) in decreasing order of prices. When a new price comes in, we pop all elements with prices less than or equal to the current price, accumulating their spans. This accumulated span plus 1 (for the current day) gives us the final span for the current price.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to find the span for a given day's price, we're essentially asking: "How many consecutive previous days (including today) had prices less than or equal to today's price?"

The naive approach would be to store all prices and, for each new price, iterate backward counting days until we find a higher price. This would take O(n) time for each query in the worst case.

But here's the key observation: if we have a day with price p1 and span s1, and a later day comes with price p2 where p2 >= p1, then all the days covered by p1's span are automatically included in p2's span. We don't need to check those days individually again - we can directly add s1 to p2's span count.

This leads us to think about maintaining only the "significant" prices - those that could potentially stop a future price's span. These are prices that form a decreasing sequence when viewed from the most recent day backward.

Consider this sequence: [7, 2, 1, 3]. When price 3 arrives:

  • We compare with 1: since 3 >= 1, we include its span
  • We compare with 2: since 3 >= 2, we include its span too
  • We compare with 7: since 3 < 7, we stop

After processing 3, we don't need to keep 1 and 2 anymore because any future price that's greater than 3 will automatically be greater than 1 and 2. The price 3 "blocks" them from being relevant to future calculations.

This pattern suggests using a stack where we maintain prices in decreasing order (from bottom to top). Each element stores both the price and its calculated span. When a new price arrives, we pop all smaller or equal prices, accumulating their spans, because all days they represent are now part of the current day's span.

Learn more about Stack, Data Stream and Monotonic Stack patterns.

Solution Approach

We implement the solution using a monotonic decreasing stack that stores pairs of (price, span).

Data Structure

  • self.stk: A stack that maintains price-span pairs in decreasing order of prices (from bottom to top)

Algorithm for next(price)

  1. Initialize span counter: Start with cnt = 1 to count the current day itself.

  2. Process the stack: While the stack is not empty AND the price at the top of the stack is less than or equal to the current price:

    • Pop the top element (old_price, old_span) from the stack
    • Add old_span to our current cnt: cnt += self.stk.pop()[1]
    • This step consolidates all consecutive days with prices ≤ current price
  3. Push current price: After processing, push (price, cnt) onto the stack. This pair represents:

    • The current price
    • The total span calculated (including all popped elements' spans)
  4. Return the span: Return cnt as the span for the current day

Why This Works

The stack maintains a decreasing sequence of prices. Each element's span represents how many consecutive days (before encountering a higher price) that price dominated.

When a new price arrives:

  • If it's higher than some stack elements, those elements' spans get absorbed into the new price's span
  • The new price then "blocks" those removed prices from affecting future calculations
  • If it's lower than the top of the stack, it simply gets added with span 1

Example Walkthrough

Let's trace through prices [100, 80, 60, 70, 60, 75, 85]:

  • next(100): Stack empty, push (100, 1), return 1
  • next(80): 80 < 100, push (80, 1), return 1
  • next(60): 60 < 80, push (60, 1), return 1
  • next(70): 70 >= 60, pop (60, 1), cnt = 1 + 1 = 2, push (70, 2), return 2
  • next(60): 60 < 70, push (60, 1), return 1
  • next(75): 75 >= 60, pop (60, 1), 75 >= 70, pop (70, 2), cnt = 1 + 1 + 2 = 4, push (75, 4), return 4
  • next(85): 85 >= 75, pop (75, 4), 85 >= 80, pop (80, 1), cnt = 1 + 4 + 1 = 6, push (85, 6), return 6

Time and Space Complexity

  • Time: O(1) amortized per operation. Each price is pushed and popped at most once.
  • Space: O(n) in the worst case when prices are strictly decreasing.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a simple example with prices [100, 80, 60, 70, 75] to see how the monotonic stack solution works.

Initial State: Stack is empty []

Day 1: price = 100

  • Stack is empty, so no elements to pop
  • cnt = 1 (just counting today)
  • Push (100, 1) onto stack
  • Stack: [(100, 1)]
  • Return: 1

Day 2: price = 80

  • Top of stack is (100, 1) where 100 > 80, so we don't pop anything
  • cnt = 1 (just counting today)
  • Push (80, 1) onto stack
  • Stack: [(100, 1), (80, 1)]
  • Return: 1

Day 3: price = 60

  • Top of stack is (80, 1) where 80 > 60, so we don't pop anything
  • cnt = 1 (just counting today)
  • Push (60, 1) onto stack
  • Stack: [(100, 1), (80, 1), (60, 1)]
  • Return: 1

Day 4: price = 70

  • Top of stack is (60, 1) where 60 <= 70, so we pop it
  • Add its span to our count: cnt = 1 + 1 = 2
  • Next top is (80, 1) where 80 > 70, so we stop popping
  • Push (70, 2) onto stack (price 70 with span 2)
  • Stack: [(100, 1), (80, 1), (70, 2)]
  • Return: 2 (meaning prices on day 3 and day 4 are both ≤ 70)

Day 5: price = 75

  • Top of stack is (70, 2) where 70 <= 75, so we pop it
  • Add its span to our count: cnt = 1 + 2 = 3
  • Next top is (80, 1) where 80 > 75, so we stop popping
  • Push (75, 3) onto stack
  • Stack: [(100, 1), (80, 1), (75, 3)]
  • Return: 3 (meaning prices on days 3, 4, and 5 are all ≤ 75)

Notice how when we popped (70, 2) in the last step, we inherited its span of 2, which already accounted for days 3 and 4. This is the key efficiency: we don't need to look back at those individual days again because the span in our stack already captured that information.

Solution Implementation

1class StockSpanner:
2    def __init__(self):
3        # Stack to maintain pairs of (price, span)
4        # The stack maintains a monotonic decreasing order of prices
5        self.stack = []
6
7    def next(self, price: int) -> int:
8        # Calculate the span for the current price
9        # Span is the number of consecutive days (including today) 
10        # where the price was less than or equal to today's price
11      
12        # Start with span of 1 (today itself)
13        span = 1
14      
15        # Pop all elements from stack that have price <= current price
16        # Add their spans to current span (they are all part of current span)
17        while self.stack and self.stack[-1][0] <= price:
18            previous_price, previous_span = self.stack.pop()
19            span += previous_span
20      
21        # Push current price and its calculated span to stack
22        self.stack.append((price, span))
23      
24        return span
25
26
27# Your StockSpanner object will be instantiated and called as such:
28# obj = StockSpanner()
29# param_1 = obj.next(price)
30
1class StockSpanner {
2    // Stack to store pairs of [price, span count]
3    // Each element represents a price and the number of consecutive days
4    // (including itself) for which it was the maximum
5    private Deque<int[]> stack = new ArrayDeque<>();
6
7    /**
8     * Constructor to initialize the StockSpanner
9     */
10    public StockSpanner() {
11    }
12
13    /**
14     * Calculates the span of the stock's price for the current day
15     * The span is defined as the maximum number of consecutive days
16     * (starting from today and going backwards) for which the price
17     * of the stock was less than or equal to today's price
18     * 
19     * @param price The stock price for the current day
20     * @return The span of the stock's price for the current day
21     */
22    public int next(int price) {
23        // Initialize count to 1 (current day always counts in its own span)
24        int count = 1;
25      
26        // Pop all elements from stack that have price less than or equal to current price
27        // and accumulate their spans
28        while (!stack.isEmpty() && stack.peek()[0] <= price) {
29            // Add the span of the popped element to current count
30            count += stack.pop()[1];
31        }
32      
33        // Push current price and its accumulated span to the stack
34        stack.push(new int[] {price, count});
35      
36        // Return the calculated span for the current price
37        return count;
38    }
39}
40
41/**
42 * Your StockSpanner object will be instantiated and called as such:
43 * StockSpanner obj = new StockSpanner();
44 * int param_1 = obj.next(price);
45 */
46
1class StockSpanner {
2public:
3    // Constructor initializes an empty stock spanner
4    StockSpanner() {
5    }
6  
7    // Calculates and returns the span of the current price
8    // Span is defined as the maximum number of consecutive days (including today)
9    // where the stock price was less than or equal to today's price
10    int next(int price) {
11        // Initialize span count to 1 (current day)
12        int span = 1;
13      
14        // Pop all elements from stack that have price less than or equal to current price
15        // and accumulate their spans
16        while (!priceStack.empty() && priceStack.top().first <= price) {
17            span += priceStack.top().second;
18            priceStack.pop();
19        }
20      
21        // Push current price and its calculated span onto the stack
22        priceStack.emplace(price, span);
23      
24        return span;
25    }
26  
27private:
28    // Stack stores pairs of (price, span)
29    // Maintains a monotonic decreasing stack of prices
30    std::stack<std::pair<int, int>> priceStack;
31};
32
33/**
34 * Your StockSpanner object will be instantiated and called as such:
35 * StockSpanner* obj = new StockSpanner();
36 * int param_1 = obj->next(price);
37 */
38
1// Stack to store [price, span] pairs
2// Each element represents a price and the number of consecutive days 
3// where the stock price was less than or equal to that price
4let priceStack: [number, number][] = [];
5
6/**
7 * Calculates the stock span for the given price.
8 * The stock span is defined as the maximum number of consecutive days 
9 * (starting from today and going backwards) for which the stock price 
10 * was less than or equal to today's price.
11 * 
12 * @param price - The current stock price
13 * @returns The span of the stock price
14 */
15function next(price: number): number {
16    // Initialize span count to 1 (current day)
17    let spanCount = 1;
18  
19    // Pop all elements from stack where price is less than or equal to current price
20    // Accumulate their spans to get the total span for current price
21    while (priceStack.length > 0 && priceStack[priceStack.length - 1][0] <= price) {
22        const [_, previousSpan] = priceStack.pop()!;
23        spanCount += previousSpan;
24    }
25  
26    // Push current price and its calculated span to the stack
27    priceStack.push([price, spanCount]);
28  
29    return spanCount;
30}
31
32/**
33 * Resets the stock spanner to initial state
34 */
35function resetStockSpanner(): void {
36    priceStack = [];
37}
38

Time and Space Complexity

Time Complexity: O(n) amortized, where n is the total number of times next(price) is called.

While a single call to next(price) might iterate through multiple elements in the stack (appearing to be O(n) worst case for that call), each element is pushed onto the stack exactly once and popped at most once throughout all operations. Therefore, across n calls to next(price), the total number of push and pop operations is at most 2n, giving us an amortized time complexity of O(1) per operation, and O(n) total for n operations.

Space Complexity: O(n), where n is the number of times next(price) is called.

In the worst case, when prices are in strictly increasing order, no elements are popped from the stack, and each call to next(price) adds one element to the stack. Therefore, the stack can grow to contain up to n elements, resulting in O(n) space complexity.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Incorrect Comparison Operator

Problem: Using strict inequality (<) instead of less than or equal to (<=) when comparing prices.

# WRONG - This will miss equal prices
while self.stack and self.stack[-1][0] < price:  # Should be <=
    previous_price, previous_span = self.stack.pop()
    span += previous_span

Why it's wrong: The span definition includes days where the price was equal to the current price, not just less than. If you use <, you'll incorrectly exclude days with the same price from the span calculation.

Solution: Always use <= for the comparison:

# CORRECT
while self.stack and self.stack[-1][0] <= price:
    previous_price, previous_span = self.stack.pop()
    span += previous_span

Pitfall 2: Forgetting to Accumulate Spans

Problem: Only counting the number of popped elements instead of accumulating their spans.

# WRONG - Only counts individual days, not accumulated spans
def next(self, price: int) -> int:
    span = 1
    while self.stack and self.stack[-1][0] <= price:
        self.stack.pop()
        span += 1  # Wrong! Should add the popped element's span
    self.stack.append((price, span))
    return span

Why it's wrong: Each element in the stack already has an accumulated span representing multiple days. By only adding 1 for each popped element, you're losing the information about the days that element represented.

Solution: Extract and add the span value from each popped element:

# CORRECT
while self.stack and self.stack[-1][0] <= price:
    previous_price, previous_span = self.stack.pop()
    span += previous_span  # Add the entire span, not just 1

Pitfall 3: Not Storing the Span with Each Price

Problem: Storing only prices in the stack without their corresponding spans.

# WRONG - Stack only stores prices
class StockSpanner:
    def __init__(self):
        self.stack = []  # Just storing prices
  
    def next(self, price: int) -> int:
        span = 1
        while self.stack and self.stack[-1] <= price:
            self.stack.pop()
            span += 1  # Can't know the actual span of popped element
        self.stack.append(price)  # Only pushing price
        return span

Why it's wrong: Without storing the span, you can't know how many days each popped price represented. This leads to incorrect span calculations when prices are popped.

Solution: Store tuples of (price, span) in the stack:

# CORRECT
self.stack.append((price, span))  # Store both price and its span
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More