Monotonic Stack & Queue Patterns
Work through examples, try the reasoning yourself, then put it into practice.
View SlidesBefore you start
- Trace a stack of array indices and distinguish strict from non-strict comparisons.
What you will practice
- Explain why a pop resolves the first later greater element.
- Derive histogram widths, equal-height ownership and the amortized linear bound.
In this lesson
Lesson
5 min readMonotonic Stack & Queue Patterns
A stack can store unresolved questions, rather than just values. You will
implement NextGreaterElements and LargestRectangleArea. Begin with an array
of indices and explain what information disappears when an index is popped.
The two exercises use different pop meanings and different equality rules.
Next greater means first later and strictly greater
Keep unresolved indices in increasing index order and nonincreasing value
order. With duplicates, “strictly decreasing” would be an inaccurate invariant.
A new value v resolves every top index j with nums[j] < v. Set answer[j]=v,
then pop j. A surviving equal value is still waiting for something larger.
Trace [3,3,1,4,2]; i:v denotes an index and its value:
| New index | Stack after processing | Newly resolved answers |
|---|---|---|
| 0 | [0:3] |
none |
| 1 | [0:3,1:3] |
none; 3 is not greater than 3 |
| 2 | [0:3,1:3,2:1] |
none |
| 3 | [3:4] |
answer[2]=4, answer[1]=4, answer[0]=4 |
| 4 | [3:4,4:2] |
none |
Output is [4,4,4,-1,-1]. Why is 4 the first later greater value for each
popped index? Every intervening value was processed already; if any had been
greater, it would have popped that index earlier. Storing only values loses the
output destination and collapses equal entries with different positions.
The -1 output is a sentinel when none exists; it may also be a real next value
for a smaller negative input, which this value-only API intentionally permits.
Histogram pops finalize a right boundary
For a histogram, keep indices of nondecreasing heights. On a strictly lower
current height, pop taller bars. Let the popped height be h, current index i,
and the new top be j (or -1 when empty). The candidate rectangle spans
j+1 ... i-1, so its width is i-j-1. The current shorter bar is excluded.
For [2,4,4,1]:
| Event | Remaining stack | Candidate |
|---|---|---|
| Read first three bars | [0,1,2] |
no right boundary yet |
| At i=3, pop index 2 | [0,1] |
h=4, width=1, area=4 |
| Still at i=3, pop index 1 | [0] |
h=4, width=2, area=8 |
| Still at i=3, pop index 0 | [] |
h=2, width=3, area=6 |
| Push index 3; reach end i=4 | [] after final pop |
h=1, width=4, area=4 |
Maximum area is 8. Notice that j can have equal height for the first pop. Calling both boundaries “strictly smaller” is wrong for this strict-pop variant. The earlier equal bar is popped later and receives the wider span. Alternatively you can pop on equality, but then reason consistently about which equal bar owns the shared span; do not combine formulas from incompatible invariants.
Process a virtual final height 0 at i=n or explicitly flush remaining bars with
right boundary n. Nonnegative zero-height bars may remain under a strict-pop
rule, but contribute no area. Do not read heights[n]: the sentinel is virtual,
and an implementation that appends its index must not dereference it afterward.
An increasing input such as [1,2,3] exposes missing flushes: its answer is 4,
not merely its last height 3. Heights times widths can exceed 32-bit integers
under the task limits; calculate in a sufficiently wide representation.
One push and one pop explain the linear bound
The inner loop may pop many indices on one iteration. Across the whole scan, each index is pushed once and popped at most once. Total pushes and pops are therefore at most 2n, giving O(n) time and O(n) auxiliary space. Counting the nested loops as O(n²) ignores that popped entries never return.
A sliding-window maximum uses the same dominance idea with a deque. Remove
expired indices <= i-k from the front. Remove smaller or equal values from the
back before adding i: a newer equal value expires later and is at least as good
for all future windows containing the old one. The front is the maximum once a
full window exists. Expiration is a separate condition from value dominance.
This is transfer practice; the two implementations in this module use stacks.
Reconstruct and diagnose
- For next-greater input
[2,2,3,1], write the stack just before 3 arrives and finish the output. What wrong answer follows from popping on equality? - For histogram
[3,3,2], the first pop at i=2 has width 1. Complete the next pop and final flush. Which bar owns the width-2 rectangle of height 3? - A window maximum implementation keeps values but no indices. On
[5,1,1,1]with k=2, how can an old 5 survive beyond its window?
Check your reconstruction
- Stack
[0,1]; output[3,3,-1,-1]. An equality pop incorrectly assigns 2 as the answer at index 0 before seeing the actual next greater 3. - Pop index 0 next: width 2, area 6. At the end pop height 2 with width 3, also area 6. The earlier equal-height bar owns the full height-3 span.
- Without its position or equivalent expiry tracking, the algorithm cannot
tell that 5 left the window. Correct window maxima are
[5,1,1].
Implement, then challenge the result with equal plateaus, all zeros, decreasing
and increasing arrays. For small histograms, enumerate every contiguous range
and calculate minimum height * width; this is an independent correctness
oracle. Later change next-greater to next-greater-or-equal and derive the changed
comparison before touching the code.
Practice sequence
Use the order below for a guided path, or choose an item to revisit. A pass records a successful attempt; try a fresh example later to check what you can reconstruct.
- Step 1
Next Greater Element
- Step 2
Largest Rectangle in Histogram
- Step 3
Monotonic Structures Checkpoint
Trace fresh cases, diagnose a failed invariant, then revisit the linked explanation and implementation.