Skip to content

Monotonic Stack

Any time you catch yourself writing a nested loop to find “the next bigger value to the right” or “how far until something taller shows up”, stop — there’s a one-pass, O(n)O(n) trick for it. A monotonic stack keeps only the indices that could still matter, discarding the rest the instant a new element proves them irrelevant.

  • The cue: “next greater element”, “next smaller element”, “days until warmer”, or anything about comparing each element to what comes after it.
  • The template: a stack of indices that stays increasing (or decreasing) in value, popped whenever the incoming element breaks that order.
  • Why every index is pushed and popped at most once, giving O(n)O(n) total despite the while loop inside the for loop.
  • How the same idea extends to histogram-area and rainwater-trapping problems.

The pattern: a decreasing stack for “next greater”

Section titled “The pattern: a decreasing stack for “next greater””

Keep a stack of indices, where the corresponding values are always decreasing from bottom to top. When a new value arrives that’s bigger than the value at the top of the stack, that new value is the “next greater element” for everything it pops — so pop them all, recording the answer for each, before pushing the new index.

next_greater_element.py
def next_greater_elements(nums):
    n = len(nums)
    result = [-1] * n     # default: no greater element exists
    stack = []             # indices, values decreasing bottom-to-top
 
    for i, x in enumerate(nums):
        while stack and nums[stack[-1]] < x:
            result[stack.pop()] = x     # x is the next greater element for this popped index
        stack.append(i)
 
    return result
 
 
nums = [2, 1, 2, 4, 3]
print(next_greater_elements(nums))   # expect [4, 2, 4, -1, -1]

Every index enters the stack exactly once (one append per loop iteration) and leaves it at most once (one pop total, ever, per index) — so despite the nested while, the total work across the whole function is O(n)O(n), not O(n2)O(n^2).

Watch the pop counter. The while loop inside the for loop looks quadratic and is not — every index is pushed once and popped at most once across the entire run:

stackThe stack holds every index still waiting for an answerLC 739 · O(n) amortised
730741752713694725766737
days to wait
········
stack (top)
empty
bottom
stack0total pops0
setupThe stack holds **indices whose answer is still unknown**, and it is kept decreasing in temperature from bottom to top. That invariant is the whole pattern: a value can only resolve the entries above it, never below.
1/24

The stack is kept decreasing from bottom to top. That invariant is what guarantees a value can only ever resolve entries above it, so nothing is examined twice.

sketch A decreasing monotonic stack scanning [2, 1, 2, 4, 3] p5.js
Each bar's index is pushed onto the stack. A new, taller bar pops every shorter bar still on the stack -- each pop resolves that index's 'next greater element'.

“How many days until a warmer temperature?” is the exact same pattern with the answer reframed as a distance (i - j) instead of the popped value itself.

daily_temperatures.py
def daily_temperatures(temperatures):
    n = len(temperatures)
    result = [0] * n       # default: no warmer day ever comes
    stack = []              # indices, temperatures decreasing bottom-to-top
 
    for i, t in enumerate(temperatures):
        while stack and temperatures[stack[-1]] < t:
            j = stack.pop()
            result[j] = i - j     # how many days until it got warmer
        stack.append(i)
 
    return result
 
 
temps = [73, 74, 75, 71, 69, 72, 76, 73]
print(daily_temperatures(temps))   # expect [1, 1, 4, 2, 1, 1, 0, 0]

Extending it: increasing stack for histogram area

Section titled “Extending it: increasing stack for histogram area”

Flip the invariant to an increasing stack (indices with values increasing bottom-to-top) and you get the classic Largest Rectangle in Histogram: when a bar shorter than the stack’s top arrives, that bar caps how far every taller bar above it could have stretched, so pop and price each one out.

largest_rectangle_histogram.py
def largest_rectangle_area(heights):
    stack = []       # indices, heights increasing bottom-to-top
    max_area = 0
 
    for i, h in enumerate(heights + [0]):   # sentinel 0 flushes the stack at the end
        while stack and heights[stack[-1]] >= h:
            height = heights[stack.pop()]
            width = i if not stack else i - stack[-1] - 1
            max_area = max(max_area, height * width)
        stack.append(i)
 
    return max_area
 
 
print(largest_rectangle_area([2, 1, 5, 6, 2, 3]))   # expect 10

Trapping Rain Water can be solved the same way too: a decreasing stack of indices, where popping a “valley” bar lets you compute the water trapped above it, bounded by the shorter of its left and right walls.

OperationComplexity
Next greater/smaller element, one passO(n)O(n) time — each index pushed and popped once
Daily TemperaturesO(n)O(n) time
Largest Rectangle in HistogramO(n)O(n) time
Space (the stack itself)O(n)O(n) worst case
  • The question is phrased as “next greater/smaller element” or “distance until a bigger/smaller value appears” — a brute-force nested loop is O(n2)O(n^2), a monotonic stack is O(n)O(n).
  • You’re computing areas or bounded regions defined by heights — histogram bars, skyline problems, rainwater trapped between bars — where each bar’s contribution depends on the nearest taller bar on each side.
  • You need, for every index, the nearest index to the left or right that breaks some ordering — the stack always holds exactly the candidates that haven’t been “resolved” yet.

Each exercise is the actual LeetCode problem with its real method signature and LeetCode’s own examples as the test. Write the body, press Run, and match the expected output — then paste the same code into leetcode.com.

Problem. nums1 is a subset of nums2. For each value in nums1, find the next greater element to its right in nums2, or -1 if none exists.

Constraints. 1 <= len(nums1) <= len(nums2) <= 1000, all values unique within each array.

Examples. nums1 = [4,1,2], nums2 = [1,3,4,2] gives [-1,3,-1] · nums1 = [2,4], nums2 = [1,2,3,4] gives [3,-1]

Editorial — approach, complexity, follow-ups

Maintain a stack of values still waiting for a greater element, kept decreasing. A new value resolves every smaller value on the stack at once, because it is the first greater element for all of them.

Time O(n+m)O(n + m) — each value is pushed once and popped at most once. Space O(n)O(n).

Values still on the stack at the end never found anything greater, which is exactly what nxt.get(n, -1) expresses — no explicit flush needed.

Keying by value works only because the problem guarantees uniqueness. With duplicates you must key by index, which is what LC 739 does.

Follow-ups you should expect: “Circular array (LC 503)?” — iterate twice over the array (or over nums * 2) so later elements can wrap around, pushing only on the first pass. “Duplicates allowed?” — store indices instead of values. “Next smaller element?” — flip the comparison to an increasing stack. “Previous greater element?” — sweep right to left.

Problem. Given daily temperatures, return an array where answer[i] is the number of days you must wait after day i for a warmer temperature. If no warmer day exists, put 0.

Constraints. 1 <= len(temperatures) <= 10^5, 30 <= temperatures[i] <= 100.

Examples. [73,74,75,71,69,72,76,73] gives [1,1,4,2,1,1,0,0] · [30,40,50,60] gives [1,1,1,0] · [30,60,90] gives [1,1,0]

Editorial — approach, complexity, follow-ups

Exactly LC 496’s sweep, with two adaptations: the stack holds indices so the answer can be a distance, and the output is pre-filled with 0 so days that never find a warmer one need no special handling.

Time O(n)O(n) — each index pushed once, popped at most once. Space O(n)O(n).

The reason this beats the O(n2)O(n^2) nested scan is that a colder day sitting under a warmer one on the stack can never be the answer for anything to the right — the warmer day would resolve it first. So each index only needs to be considered once.

Because temperatures may repeat, the comparison is strict (< t): an equal temperature is not warmer, so it must not resolve the day beneath it. That is why indices rather than values are needed here where LC 496 could use values.

Follow-ups you should expect: “Do it right to left instead?” — also works, and you can skip forward using already-computed answers. “Previous warmer day?” — sweep in the other direction. ”O(1)O(1) space?” — not possible in general; you must remember unresolved days. “Why does the stack stay decreasing?” — anything that would break the order is popped first, by construction.

LC 84 — Largest Rectangle in Histogram · Hard

Section titled “LC 84 — Largest Rectangle in Histogram · Hard”

Problem. Given bar heights of width 1 each, return the area of the largest rectangle that fits inside the histogram.

Constraints. 1 <= len(heights) <= 10^5, 0 <= heights[i] <= 10^4.

Examples. [2,1,5,6,2,3] gives 10 (heights 5 and 6, width 2) · [2,4] gives 4 · [5,4,3,2,1] gives 9

Editorial — approach, complexity, follow-ups

Every maximal rectangle is limited by some bar’s height. For each bar, the widest rectangle at that height extends left and right until it meets something shorter.

An increasing stack finds both boundaries at the right moment: when a bar is popped because a shorter bar arrived, the right boundary is the current index, and the left boundary is just past whatever is now on top of the stack — which is the nearest shorter bar to its left.

Time O(n)O(n). Space O(n)O(n).

Three details:

  • The sentinel 0. Iterating heights + [0] guarantees every remaining bar is popped and priced. Without it, an increasing histogram like [2,4] leaves everything on the stack unpriced and returns 0.
  • left = stack[-1] + 1 if stack else 0. An empty stack means nothing shorter exists to the left, so the rectangle reaches index 0.
  • >= rather than >. With equal heights, popping the earlier one is safe: it will be re-priced through the later equal bar, which has the same height and a wider span. [2,2,2] giving 6 confirms it.

[5,4,3,2,1] giving 9 is a good trace — the answer is height 3 across width 3, found mid-sweep rather than at a boundary.

Follow-ups you should expect: “Maximal Rectangle in a binary matrix (LC 85)?” — build a histogram per row (heights of consecutive ones above) and run this on each row; O(mn)O(mn) overall. “Why >= and not >?” — the equal-heights argument. “Trapping Rain Water (LC 42)?” — related but the geometry differs: you accumulate water above each position rather than rectangles beneath. “Divide and conquer?” — recurse on the minimum bar; O(nlogn)O(n \log n) average but O(n2)O(n^2) worst case.

Generated from the problem database, so each entry carries its sheet membership and reported companies. Tick them off as you go — progress is saved in this browser, and the Export button writes it to a file you can keep.

7 problems
1 easy4 medium2 hard

Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.

  • 496Next Greater Element IeasyThe exact decreasing-stack template (II adds a circular array twist)
  • 739Daily TemperaturesmediumNext-greater-element, answering with a distance instead of a valueNeetCode 150amazonmetabloomberg
  • 503Next Greater Element IImediumThe exact decreasing-stack template (II adds a circular array twist)
  • 853Car FleetmediumNeetCode 150
  • 907Sum of Subarray MinimumsmediumFor each element, use a monotonic stack to find how many subarrays it's the minimum of, then sum `value * count` over all elements
  • 42Trapping Rain WaterhardA decreasing stack of "walls", computing water trapped above each popped valleyNeetCode 150LeetCode Top Interview 150amazongooglemetabloomberg
  • 84Largest Rectangle in HistogramhardThe increasing-stack variant, pricing out each bar's maximal rectangle as it gets poppedNeetCode 150googleamazonbytedance

LC 739 with temps = [73, 74, 75, 71, 69, 72, 76, 73]. The stack column shows indices; the values are in brackets.

itemppops (resolved)stack afterout so far
073[0(73)][0,0,0,0,0,0,0,0]
1740out[0]=1[1(74)][1,0,…]
2751out[1]=1[2(75)][1,1,0,…]
371[2(75), 3(71)]unchanged
469[2, 3, 4(69)]unchanged
5724out[4]=1, 3out[3]=2[2(75), 5(72)][1,1,0,2,1,0,0,0]
6765out[5]=1, 2out[2]=4[6(76)][1,1,4,2,1,1,0,0]
773[6(76), 7(73)]unchanged

Two things to read off that table:

  • Total pops: 6. Total pushes: 8. Fourteen stack operations for an eight-element array — not 64. That is the amortised argument, stated concretely, and it is the answer to “why is this O(n) when there is a while inside a for”.
  • Indices 6 and 7 are still on the stack at the end. They never saw a warmer day, so their answers stay 0. Leftovers on the stack are not a bug; they are the “no answer exists” case, and pre-filling out with zeros is what handles them.

Note step 5 popping two entries in one iteration while step 3 pops none. The work per iteration is wildly uneven; only the total is bounded.

One template, four mutations. The direction of the comparison and what you store are the only things that change.

VariantStack orderingPop conditionCanonical problem
Next greaterdecreasingstack top < current739 Daily Temperatures · 496 Next Greater Element I
Next smallerincreasingstack top > current1475 Final Prices With Special Discount
Previous greater / smallersame, but scan right-to-leftmirror image901 Online Stock Span
Span bounded on both sidesincreasing, store indicespop while top >= current, and compute width on pop84 Largest Rectangle · 42 Trapping Rain Water · 85 Maximal Rectangle
  • Storing values instead of indices. out[j] = i - j needs j. Once you have pushed only the value, the position is gone and distance questions become unanswerable. Push indices; read values through them.
  • Using < where <= belongs, or the reverse. With equal values, <= pops the earlier equal element and < keeps it. For “days until strictly warmer” that choice changes the answer on flat runs like [73, 73, 74].
  • Forgetting the leftovers. Anything still on the stack has no answer. Pre-fill the output with the default (0, or −1) rather than trying to handle it afterwards.
  • Omitting the sentinel in span problems. LC 84 and LC 85 both need it. This produces a plausible answer that is wrong only on some inputs, which is the hardest kind of bug to spot in an interview.
  • Claiming O(n)O(n) without being able to justify it. The nested while looks quadratic. Be ready to say: each index is pushed exactly once and popped at most once, so total stack operations are bounded by 2n2n regardless of how uneven individual iterations are.
They askWhat they’re checkingThe answer
“There’s a while inside a for — why is it O(n)O(n)?”Amortised reasoningEach index is pushed once and popped at most once, so total stack operations are at most 2n2n. Individual iterations vary wildly; only the total is bounded
“What is on the stack, in words?”Whether you understand the invariantEvery index whose answer is not yet known, kept in decreasing value order — so a new element resolves a prefix of them and never has to look deeper
“Make it work on a circular array”AdaptabilityLoop i over 2n and index i % n, pushing only while i < n
“Now I want the largest rectangle in the histogram”Whether you see the same patternIncreasing stack, compute width on each pop, and append a sentinel 0 so every bar is forced out and measured
“Can you do it in O(1)O(1) space?”Whether you know the limitsNot in general — the stack can hold nn entries (a strictly decreasing input). For the count of pops only, sometimes; for per-element answers, no
“Same problem, but as a stream”Practical modellingWorks unchanged for “previous greater” (LC 901 Stock Span). “Next greater” cannot be answered in a stream at all, because the answer depends on the future
pch.quizTag Monotonic stack — self-check
  1. What do the entries on a monotonic stack represent?

    pch.quizShowAnswer

    B — Indices whose answer is still unknown, kept in decreasing value order — This is the invariant the whole pattern rests on. Because entries are ordered by value, a new element resolves a prefix of them and never needs to look deeper — which is what makes the pop loop cheap in aggregate.

  2. The pop loop is nested inside the scan loop. Why is the algorithm still O(n)?

    pch.quizShowAnswer

    B — Each index is pushed once and popped at most once, so total stack operations are bounded by 2n — An amortised argument, not a per-iteration one. One iteration may pop five entries and the next may pop none; the bound is on the sum across the whole run.

  3. Why push indices rather than values?

    pch.quizShowAnswer

    B — Because distance answers like out[j] = i - j need j, and a value cannot recover its position — Push indices and read values through them. The moment a problem asks 'how far' rather than 'what value', storing values makes the question unanswerable.

  4. In Largest Rectangle in a Histogram, why append a sentinel height of 0?

    pch.quizShowAnswer

    B — To force every bar still on the stack to pop and have its rectangle measured, since it has no right boundary otherwise — A bar left on the stack never got a right boundary, so its rectangle is never computed. The sentinel is smaller than every real height, so it forces all of them out. Omitting it gives an answer that is too small on non-decreasing inputs — a wrong answer that passes many tests.

  5. The problem is 'next greater element in a circular array'. What is the clean approach?

    pch.quizShowAnswer

    B — Iterate i over 2n and index with i % n, pushing only while i < n — Both work, but the modulo version uses no extra memory. Concatenating costs O(n) space for no benefit, and mentioning the difference is a cheap signal of fluency.

  • Cue — “next/previous greater or smaller”, or a span bounded by smaller values on both sides.
  • Invariant — the stack holds indices whose answer is unknown, in decreasing (or increasing) value order.
  • Template — for each i: while stack and cmp(a[stack[-1]], a[i]): j = pop; out[j] = f(i, j), then push(i).
  • ComplexityO(n)O(n) amortised (2n\le 2n stack operations), O(n)O(n) space worst case.
  • Remember — push indices; pre-fill the output for the leftovers; span problems need a sentinel.
  • Fails when — the query is about a window (use a monotonic deque) or a k-th value (use a heap).
  • A monotonic stack holds indices in increasing or decreasing order of their values, popping whenever a new element breaks that order.
  • Decreasing stack -> “next greater element” style questions. Increasing stack -> histogram/area style questions where a short bar caps taller ones.
  • Every index is pushed once and popped at most once, so the total work is O(n)O(n) even with a while loop nested inside the main for loop.
  • The same scaffolding solves next-greater/smaller, daily temperatures, histogram area, rainwater trapping, and subarray-minimum-sum questions — only what you compute on a pop changes.

Next: Prefix Sums and Difference Arrays — answering range-sum queries in O(1)O(1) after a single O(n)O(n) preprocessing pass.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading