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 scan. We trace the next-greater-element routine push by push and prove its amortized bound, fuse two such scans to measure the largest rectangle in a histogram in linear time, extend the idea to a monotonic deque that streams the sliding-window maximum in , and use asymmetric tie-breaking to count subarray minimums without double-counting duplicates.
╌╌╌╌
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.
- 1empty stack of indices
- 2for to do
- 3while not empty and do
- 4
- 5: 's next-greater
- 6unresolved
- 7while not empty do
- 8no greater right
The scan, push by push
Here is the complete run on . The stack is written bottom to top; each entry is an index with its value in parentheses.
| pops and assignments | stack after (bottom top) | ||
|---|---|---|---|
| — | |||
| pop : | |||
| — | |||
| — | |||
| pop : ; then pop : | |||
| end | — | pop , then : |
Read each row against the invariant: the value column of the stack is strictly decreasing at every moment ( before step ; then after). Step shows the payoff. The arrival of resolves two waiting indices at once: value (index ) and value (index ) both see their first larger value to the right, in that order, top of stack first. Then blocks further popping — value may yet be the next-greater of something later, and itself now waits beneath it. Indices and survive to the end and get : nothing to their right ever beat them.
The body of the while looks as if it could be quadratic, but it is not.
The same bound falls out of the potential method with stack size: an iteration that pops elements costs real work but decreases the potential by , so its amortized cost is . Either way, the expensive steps are expensive precisely because many earlier steps were cheap — the trace above pays for step 's double pop with the pushes at steps and .2
Strictness, and the four cousins
The pop condition is strict, so equal values do not
resolve each other: on the second does not pop
the first — both wait, stacked, until discharges them together. That is the
right behavior for next strictly greater
. If the task is next greater or equal
, pop on instead. One comparison character
changes which of the two s answers for which subarrays, and the section on
counting subarray minimums
shows why that choice is sometimes forced.
Flipping the comparison to , with a strictly-increasing stack, computes the next smaller element; reversing the scan direction gives previous greater / previous smaller. Better still, the previous-side answers come for free in the same pass: at the moment is pushed, the element just beneath it is the nearest earlier index that survived the pops — with pop-on- that is the previous greater-or-equal element, and with pop-on- it is the previous strictly greater. Popping resolves the next side; pushing reads off the previous side, with the strictness complemented. 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.
For a circular array (Next Greater Element II), run the identical scan over , comparing against but pushing only while : the first pass builds the stack, the second lap only resolves the leftovers that needed to wrap around.
Largest rectangle in a histogram
Given bar heights of unit width, find the largest axis-aligned rectangle that fits under the skyline. 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 the widest one of height .
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. The sentinel is a correctness requirement: bars still on the stack after have no shorter bar to their right, so their rectangles run to the array boundary — a height- bar at position is shorter than every real bar and discharges each of them with . Without it, a strictly increasing histogram would trigger no pops at all and report .
- 1empty stack of indices; append sentinel
- 2
- 3for to do
- 4while not empty and do
- 5
- 6( empty )previous-smaller bar
- 7
- 8
- 9return
The whole computation, worked
Take , the skyline in the figures, with the sentinel appended.
| pops: , , | stack after | best | ||
|---|---|---|---|---|
| — | ||||
| , : | ||||
| — | ||||
| — | ||||
| , : ; , : ; , : | ||||
| , : |
Step carries the whole argument. Bar (height ) is shorter than the entire stacked run , so three rectangles are measured in sequence, each with its exact left boundary read off the stack after its pop: bar alone (, hemmed in by bar on the left), bars – at height (), and bars – at height ( — the stack is empty after popping index , so the rectangle runs all the way to the left edge, ). The best, , is achieved twice with different shapes. The sentinel's only job here is bar itself, which extends the full width for .
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 because the increasing stack hands us both the left and
right smaller-boundaries for free.
Equal heights deserve a look. The pop condition is (non-strict), so an arriving bar pops earlier bars of the same height. On plus sentinel: pops bar and records , understating that bar's true reach; then the sentinel pops bar with the stack empty and records , the correct answer. The pattern is general: within a run of equal-height bars, every bar but the last gets a truncated width, and the last one measures the full rectangle. The maximum is therefore always right, even though the per-bar widths are not — a distinction that matters the moment you need each bar's exact span, as in the counting problems below.
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 :
- 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.
- Expire the front. If the front index has fallen out of the window (), pop it from the front.
A full stream
The run on with ; deque entries are index (value), front first, and the first output appears once the window is full at .
| back pops (rule 1) | expiry (rule 2) | deque after (front back) | window: max | ||
|---|---|---|---|---|---|
| — | — | — | |||
| pop | — | — | |||
| pop | — | : | |||
| — | — | : | |||
| pop | — | : | |||
| pop , then | — | : |
Index (value ) fronts three consecutive windows while smaller values come and go behind it — arrives and queues up (it would be the max if expired), then evicts and queues up itself. At the value clears the whole deque from the back before ever ages out. The deque always reads strictly decreasing left to right, and its front is the answer.
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
When the front ages out
In the trace above, rule 2 never fired: every candidate was out-valued from the back before it could grow stale. The opposite happens on descending input. Run with : nothing pops from the back during the descent , so the deque fills up with the whole run — each value is a live candidate, since all the larger ones ahead of it will expire first. At the front index satisfies , so is expired by age, not by value, and the reported max steps down to . Then arrives and clears the entire remainder from the back in one burst.
Ties again hide a decision. Rule 1 pops while the back is , equal values included, and deliberately so: the newer of two equal values survives at least as long in the window, so the older one is dominated and can go. Popping on instead, keeping equal values queued, still reports correct maxima — it just lets the deque carry duplicates it will never need. The choice becomes visible only when the problem asks which index achieves the maximum: reports the latest tied index, the earliest.
Counting with spans: sum of subarray minimums
The same previous/next-smaller machinery solves an entirely different-looking problem: compute over all subarrays. Summing subarray by subarray is at best. Instead, flip the accounting to count contributions: each position contributes , where is the number of subarrays whose minimum is the element at . A subarray has as its minimum exactly when it contains and stays strictly inside the region where is smallest, so is a product of two span lengths:
where is the nearest index to the left with a value strictly smaller than (or ), and the nearest index to the right with a value smaller or equal (or ). The subarray's left end can sit anywhere in and its right end anywhere in , independently. Both span arrays are single monotonic-stack passes, so the whole sum is .
On :
| left right | contribution | ||||
|---|---|---|---|---|---|
Total , which matches brute force: the ten subarrays have minimums , summing to . Position dominates because value is the minimum of every subarray containing it: choices of left end times choices of right end.
The asymmetry — strict on the left, non-strict on the right — is what makes the count exact in the presence of duplicates. Take , whose three subarrays (, , ) all have minimum , for a sum of . With the asymmetric rule, position gets (the equal value counts as a right boundary) so , and position gets (the equal value does not count as a left boundary) so ; total . Make both sides strict and both positions claim the subarray : , , total — double-counted. Make both sides non-strict and neither claims it: total — missed. The asymmetric rule assigns every subarray's minimum to exactly one position, the rightmost occurrence of the minimal value inside it.
The identical pattern with comparisons flipped computes the sum of subarray
maximums, and the difference of the two sums answers sum of (max min) over all subarrays
in one linear pass each.
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 a problem reduces to nearest element on one side beating the current one,
use the monotonic stack (or, for windowed maxima, the
monotonic deque): one push and one pop per element, total.
Choosing the tool
The variants differ only in stack order and one comparison. To read the table: the popped element's next-side answer is the arriving index , and the pushed element's previous-side answer is whatever the new top is, with the strictness complemented.
| target (to the right) | stack values | pop while |
|---|---|---|
| next strictly greater | decreasing | |
| next greater or equal | decreasing | |
| next strictly smaller | increasing | |
| next smaller or equal | increasing |
For maxima over a moving window, trade the stack for a decreasing deque (an increasing deque for windowed minima). The recurring mistakes:
- Push indices, not values. Widths (), distances (), and deque expiry all need positions; the values are one array lookup away.
- Choose strictness per side, deliberately. For a single next-greater query any consistent choice works; for counting problems, symmetric choices double-count or miss subarrays whose extreme value appears more than once.
- Do not forget the histogram sentinel. Bars surviving the scan still owe a rectangle that reaches the right edge; a strictly increasing input pops nothing without the sentinel and returns .
- Expire the deque front by index arithmetic (), never by comparing values, and emit window outputs only from on.
- Never re-push a popped element. The bound amounts to the statement
that each index is pushed once and popped at most once; any
put it back and retry
variation forfeits linearity.
Cartesian trees, monotonic queues, and maximal rectangles
The monotonic stack is the algorithmic core of a data structure textbooks treat
separately: the Cartesian tree (Vuillemin, A Unifying Look at Data
Structures, CACM 1980). Build a Cartesian tree of an array — a binary tree that
is a min-heap by value and an in-order traversal by index — and its parent-child
links encode the nearest smaller to the left/right
relations the
monotonic stack computes; in fact the standard Cartesian-tree
construction is a monotonic-stack sweep. That connection is what links this
lesson to two others: the range-minimum-query problem reduces to lowest-common-
ancestor on the Cartesian tree, and the tree's structure underlies the
treap (tree + heap) balanced-BST variant.
The sliding-window-maximum deque generalizes to the monotonic queue
optimization for dynamic programming: a DP recurrence of the form
, where the window
of valid slides forward, is evaluated in instead of by
keeping the candidates in a monotonic deque — the same expire-the-front,
pop-the-dominated logic, applied to DP values rather than array elements. This is
the standard speedup for problems like jump game with a bounded reach
and
appears in the DP-optimizations
material as the deque case of the more general convex-hull and Knuth
optimizations.
The largest-rectangle-in-a-histogram routine, finally, is the one-dimensional kernel of the maximal-rectangle problem on a binary matrix: process the matrix row by row, maintaining for each column the height of the run of ones ending at the current row, and run the histogram scan on each row's height array — an algorithm for an matrix that would otherwise look hopelessly combinatorial.
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 height- sentinel flushes the final increasing run.
- 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 .
- Duplicates are a tie-breaking decision. Reporting a single extreme tolerates either strictness; counting each subarray once requires strict on one side, non-strict on the other, as in the sum of subarray minimums and, implicitly, the fused histogram scan.
- Daily temperatures, stock span, and trapping rain water are the same previous/next-greater machinery pointed in different directions.
Footnotes
- Skiena, §3.2 — Stacks and Queues: stacks and queues as the primitives behind LIFO/FIFO scans; the monotonic discipline turns them into linear nearest-greater solvers. ↩
- CLRS, Ch. 16 — Amortized Analysis: aggregate and potential-method bounds; the stack whose elements are each pushed and popped at most once is the canonical example. ↩
- Skiena, §3.2 — Stacks and Queues: the deque (double-ended queue) supporting push/pop at both ends, the structure underlying sliding-window maxima. ↩
╌╌ END ╌╌