Skip to content
Work Free practice Coding course Blog Method Results Why me About Enquire Book a call

Coding · Stack

Understanding the problem

Lesson · 7 min read · Stack

A question asked once per element

A trader wants to know, for each day's closing price, how many days pass before the price is higher. The natural first answer walks forward from every day:

for i in range(n):
    for j in range(i + 1, n):
        if price[j] > price[i]:
            wait[i] = j - i
            break

On a rising market this is fast, because every inner loop stops after one step. On a falling market it is a disaster: no day ever finds a higher price, and every inner loop runs to the end of the array.

How bad is the bad case?

On a steadily falling series the inner loop runs n − 1, then n − 2, and so on: about n²/2 comparisons in total.

Days of pricesComparisons on a falling seriesAt 10⁸ a second
1,000≈ 500,000instant
100,000≈ 5 billionabout a minute
1,000,000≈ 500 billionwell over an hour

Worse, the work is wasted in a specific way. When day 5 scans forward past days 6, 7 and 8, it learns that each of them is lower than day 5. Then day 6 scans the same stretch again and learns almost the same thing. The brute force keeps no memory of what it has already seen.

Turn the question round

Instead of each day going to look for its answer, let the answers come to the days. Walk through the prices once and keep a list of the days that have not yet seen a higher price. When a new price arrives, it is the answer for every waiting day it beats.

Which waiting days does it beat? Always the most recent ones. A day can only still be waiting if nothing after it was higher, so the waiting prices fall from oldest to newest. The new price removes waiting days from the newest end until it meets one it does not beat, then joins the list itself. Adding and removing at the same end is a stack.

The same prices, handled with a list of waiting days. Watch how a single new price can settle several days at once.

  1. Step 1 / 6
    price
    730i741752713694725766
    waiting
    730
    settled0

    Day 0, price 73. Nothing is waiting. Day 0 starts waiting.

  2. Step 2 / 6
    price
    730741i752713694725766
    waiting
    740
    settled1

    Day 1, price 74, beats 73. Day 0 gets its answer, 1 day. Day 1 waits.

  3. Step 3 / 6
    price
    730741752i713694725766
    waiting
    750
    settled2

    Day 2, price 75, beats 74. Day 1 settled. Day 2 waits.

  4. Step 4 / 6
    price
    730741752713694i725766
    waiting
    750711692
    settled2

    Days 3 and 4 (71, then 69) beat nothing. They pile up on top of 75, in falling order.

  5. Step 5 / 6
    price
    730741752713694725i766
    waiting
    750721
    settled4

    Day 5, price 72, beats 69 and then 71, but not 75. Two days settled in one visit; 75 stays.

  6. Step 6 / 6
    price
    730741752713694725766i
    waiting
    760
    settled6

    Day 6, price 76, beats 72 and 75. Everything that could be settled is settled, after one pass.

Why it is linear

There is a while loop inside the for loop, so it looks quadratic. Count differently: every day is added to the stack once and removed at most once. However the removals are spread across the iterations, their total over the whole run is at most n. That style of argument, charging the work to the elements rather than to the loop iterations, is called amortised analysis, and interviewers expect you to give it whenever you write a loop like this.

The stack holds questions that are still open. Each new element closes some of them and opens one of its own. When a problem asks about the nearest larger or smaller element, or the matching partner of something seen earlier, that is the picture to reach for.