Sequences & Strings/Monotonic Stacks & Queues

Lesson 5.21,483 words

Monotonic Stacks & Queues

A monotonic stack keeps its contents sorted by popping every element that would break the order before each push — turning a family of "previous/next greater (or smaller) element" questions into a single O(n)O(n) scan. We derive the next-greater-element routine and its amortized analysis, fuse two such scans to measure the largest rectangle in a histogram in linear time, and extend the idea to a monotonic deque that streams the sliding-window maximum in O(n)O(n).

╌╌╌╌

A plain stack records history in last-in-first-out order; a monotonic stack records only the useful history. The rule is one extra line: before pushing a new element, pop every element already on the stack that would violate a chosen order, increasing or decreasing, and only then push. What survives on the stack is always a sorted sequence, and the elements you discard are discarded exactly when they stop being able to influence any future answer. That single discipline collapses a whole family of array questions (what is the next value to my right larger than me?, how far back is the last taller bar?, what is the maximum of every length- window?) from quadratic brute force to a single linear pass.1

The questions all share a shape. For each index we want the nearest index to one side whose value beats in some sense (greater, smaller). Brute force re-scans from every and costs . The monotonic stack avoids the re-scan by carrying, at all times, precisely the set of indices whose answer is still unknown, discharging each one the instant its answer appears.

The monotonic stack: next greater element

The canonical task: for each , find , the smallest with (or none). Scan left to right keeping a stack of indices whose next-greater is still unknown, maintained so their values are strictly decreasing from bottom to top. When we reach :

If exceeds the value at the top, then is the answer for that top index, the first larger value to its right, so we pop it, record , and repeat. We keep popping while beats the new top; each popped index has just found its next-greater. Once the top is (order restored), we push , still unresolved. Anything left on the stack at the end has no greater element to its right.

Algorithm:Next-Greater(a[1..n])\textsc{Next-Greater}(a[1..n]) — next strictly-greater element to the right
  1. 1
    SS \gets empty stack of indices
  2. 2
    for i1i \gets 1 to nn do
  3. 3
    while SS not empty and a[top(S)]<a[i]a[\text{top}(S)] < a[i] do
  4. 4
    jpop(S)j \gets \text{pop}(S)
  5. 5
    nge[j]i\text{nge}[j] \gets i
    a[i]a[i]: jj's next-greater
  6. 6
    push(S,i)\text{push}(S, i)
    unresolved
  7. 7
    while SS not empty do
  8. 8
    nge[pop(S)]none\text{nge}[\text{pop}(S)] \gets \text{none}
    no greater right

The body of the while looks as if it could be quadratic, but it is not.

Next-greater scan on . Arriving pops indices then (values both ), assigning ; index (value ) survives, then is pushed

The same scan, with the comparison flipped to and a strictly-increasing stack, computes the next smaller element; reversing the scan direction gives previous greater / previous smaller. Four cousins, one template. Daily Temperatures is literally returning instead of ; the classic stock span is previous-greater; and as we will see, trapping rain water is bounded on each side by the previous- and next-greater bars. They are all the same machine pointed in different directions.2

Largest rectangle in a histogram

Given bar heights of unit width, find the largest axis-aligned rectangle that fits under the skyline. The key observation: the maximal rectangle using bar as its limiting (shortest) height extends left until it hits the nearest shorter bar and right until the nearest shorter bar. So if is the previous-smaller index and the next-smaller index, bar contributes a rectangle of height and width , and the answer is the maximum of these over all . That is two monotonic scans, and we can fuse them into one.

Keep an increasing stack of bar indices. When bar arrives shorter than the top, the top bar can extend no further right: is its next-smaller bar. Pop it. Its previous-smaller bar is exactly the new stack top after the pop, because the stack is increasing, so the element now exposed beneath it is the closest earlier bar shorter than the popped one. So at the moment of popping index with as the trigger:

where the width spans from just past the previous-smaller bar (the exposed stack top) up to just before the next-smaller bar . Both boundaries are pinned to genuine smaller bars, so the rectangle is exactly the widest one of height .

Largest rectangle via a monotonic increasing stack: bar 5 triggers the pops; the maximal rectangle of height spans bars 1–4

When the trigger bar is shorter than several stacked bars, we pop them one after another, each discharged with its own correct width, before pushing the trigger. A sentinel of height appended at the end flushes everything still on the stack.

Algorithm:Largest-Rectangle(h[1..n])\textsc{Largest-Rectangle}(h[1..n]) — fused previous/next-smaller scan
  1. 1
    SS \gets empty stack of indices; append sentinel h[n+1]0h[n+1] \gets 0
  2. 2
    best0\text{best} \gets 0
  3. 3
    for i1i \gets 1 to n+1n+1 do
  4. 4
    while SS not empty and h[top(S)]h[i]h[\text{top}(S)] \ge h[i] do
  5. 5
    tpop(S)t \gets \text{pop}(S)
  6. 6
    LL \gets (SS empty ?? 00 :: top(S)\text{top}(S))
    previous-smaller bar
  7. 7
    bestmax(best,  h[t](iL1))\text{best} \gets \max(\text{best},\; h[t] \cdot (i - L - 1))
  8. 8
    push(S,i)\text{push}(S, i)
  9. 9
    return best\text{best}

Every index is pushed once and popped once, so the histogram is solved in amortized time and space, by the same aggregate argument as before. What was a try every pair of boundaries search becomes a single sweep precisely because the increasing stack hands us both the left and right smaller-boundaries for free.

The monotonic deque: sliding-window maximum

Now stream the maximum of every contiguous sliding window of width : for each start . A binary heap of the current window gives , since every slide does an insert and a (lazy) delete. A monotonic deque does it in .

Maintain a double-ended queue of indices whose values are strictly decreasing from front to back. Two rules per step at index :

  1. Push back, popping smaller tails. While the back's value is , pop it, since it can never again be a maximum: is at least as large and stays in the window at least as long. Then push at the back.
  2. Expire the front. If the front index has fallen out of the window (), pop it from the front.

After both rules, the front holds the index of the maximum of the current window: it is the largest value among all still-live candidates, because every larger-or-equal later value would have evicted it, and every earlier larger value that left the window was expired.

Sliding-window maximum with a decreasing deque; window over , deque front (in accent) is the window max

Each index enters the deque once (one push) and leaves once (one pop, from either end), so across the whole stream the deque does work, independent of . That beats the heap's and, unlike the heap, never carries stale elements, since out-of-window indices are expired from the front the moment they become irrelevant.3

Deque step at () on . The incoming exceeds both tails, so back-popping clears indices (values ); the deque collapses to , the new window- maximum

Why one idea covers so many problems

Stock span, daily temperatures, largest rectangle, maximal rectangle in a binary matrix (a histogram per row), and trapping rain water are all the same previous/next-greater-or-smaller machinery. Trapping rain water, for instance, holds water above index up to of the tallest bar to its left and the tallest bar to its right: two directional maxima that a monotonic stack supplies in one pass each. Once you recognize nearest element beating me on one side, the monotonic stack (or, for windowed maxima, the monotonic deque) is the reflex: one push and one pop per element, total.

Trapping rain water: above each bar the water rises to , so the trapped height at index is — bounded on each side by a previous/next taller bar

Takeaways

  • A monotonic stack stays sorted by popping order-violating elements before each push; at every moment it holds exactly the indices whose answer is still unresolved.
  • Next greater element is the core routine: a decreasing stack of indices, resolved when a larger value arrives. By an aggregate argument (each index pushed once, popped at most once) it runs in amortized .
  • Largest rectangle in a histogram fuses a previous-smaller and a next-smaller scan into one increasing stack: a popped bar's left/right limits are the exposed stack top and the trigger index, both genuine smaller bars, so the linear sweep computes every maximal rectangle.
  • A monotonic (decreasing) deque streams the sliding-window maximum in : push at the back popping smaller tails, expire the front when it leaves the window, and the front is always the window max, beating a heap's .
  • Daily temperatures, stock span, and trapping rain water are the same previous/next-greater machinery pointed in different directions.

Footnotes

  1. Skiena, § — Stacks & Queues: stacks and queues as the primitives behind LIFO/FIFO scans; the monotonic discipline turns them into linear nearest-greater solvers.
  2. Erickson, Ch. — Data Structures: amortized aggregate analysis of stack operations where each element is pushed and popped at most once.
  3. Skiena, § — Stacks & Queues: the deque (double-ended queue) supporting push/pop at both ends, the structure underlying sliding-window maxima.
Practice

╌╌ END ╌╌