Monotonic Stacks: Next Greater Element

Stacks & Monotonic Stacks, lesson 3 of 3

Monotonic Stacks: Next Greater Element

Answer 'next greater / smaller' for every element in one O(n) pass.

14 min, 0 of 3 activities solved

View cheatsheet

Getting Python ready… examples can run in a moment.

The question: for every element, find the first element to its right that is bigger (-1 if none).

nums   = [2, 1, 5, 3, 4]
answer = [5, 5, -1, 4, -1]

The brute force scans right from every index: O(n²). Notice the waste: while scanning for 2 we walk past 1 and 5, then scan for 1 and walk past 5 all over again.

The idea: walk left to right and keep a stack of indices that are still waiting for their answer. When a new value x arrives, it is the answer for every waiting element smaller than it - pop them all and record x. Then x itself starts waiting.

i  x   pops (answer = x)   stack (values)
0  2   -                   [2]
1  1   -                   [2, 1]
2  5   1 -> 5, 2 -> 5      [5]
3  3   -                   [5, 3]
4  4   3 -> 4              [5, 4]
end: 5 and 4 never answered -> -1

Look at the stack column: the values are always decreasing from bottom to top. If a bigger value sat above a smaller one, it would already have popped it. That ordering is why it's called a monotonic stack, and why we only ever need to look at the top.

Example

Brute force vs monotonic stack

Change res[j] = x to res[j] = i - j and you get "how many steps until something bigger" - that's Daily Temperatures.

Why is it O(n) with a while inside a for? Count the work per index instead of per loop: each index is pushed exactly once and popped at most once. So across the whole run there are at most 2n stack operations. That's an amortized argument: one step might pop many items, but only because earlier steps pushed them.

Quiz

What does this print?

Variations - same loop, tweak one thing:

  • Next smaller: pop while nums[top] > x. The stack is now increasing bottom to top.
  • Previous greater / smaller: after popping, whatever is on top (if anything) is the answer for the current index.
  • Equal values: < vs <= decides whether a tie counts as "greater". Read the problem carefully.
  • Circular array: loop i over range(2 * n) and use nums[i % n].
  • Not just arrays: any "who blocks whom" scan, e.g. cars catching up, or bars in a histogram that limit a rectangle's height.
Example

Previous smaller: read the top after popping

Here storing values is fine because we only report values. For the largest-rectangle problem you'd need the indices, to measure widths.

Quiz

You scan left to right to find each element's next SMALLER element. Reading the stack's values from bottom to top, they are always...

Exercise

Next smaller element

Write next_smaller(nums) that returns, for every index, the first value to its right that is strictly smaller, or -1 if there is none.

[4, 8, 5, 2, 25] -> [2, 5, 2, -1, -1]

Use a monotonic stack of indices so it runs in O(n).