Chapter 7: Foundational Problem-Solving Patterns
7.05Beginner6 topics20 min5 interactive animations

Monotonic structures

A monotonic stack or deque keeps its elements in increasing or decreasing order by throwing away elements that can never matter again. It answers “nearest larger or smaller” and “window maximum” questions in O(n).

Learning goals

  • Find the next and previous greater or smaller element for every index
  • Explain why each element is pushed and popped at most once
  • Maintain a sliding window maximum with a monotonic deque
  • Choose between strict and non-strict comparisons for duplicates

Prerequisites

Stacks and dequesSliding windows

Standing in a line of people, which taller person blocks your view to the right? Anyone shorter than a newcomer will be hidden behind that newcomer for everyone further left, so you can forget them.

7.05.1

Next greater

Scan left to right and keep a stack of indices still waiting for their answer. Their values decrease from bottom to top. When a new value is larger than the top, it is the next greater element for that index: pop it and record the answer, and repeat.

Indices left on the stack at the end have no greater value to their right and get -1. Try the “Increasing” preset (each element is answered by its right neighbour, except the last) and the “Decreasing” one (nothing is ever answered, so everything stays -1).

Worst

O(n)

Space

O(n)

Next greater element (monotonic stack)

Interactive · edit the input

Your input

Try

int[] arr · length 8

4
7
3
5
9
2
6
8
01234567
CurrentOn the stackAnswered

stack (bottom→top)

empty

result[]

·0
·1
·2
·3
·4
·5
·6
·7
Setup

The stack holds indices whose values decrease from bottom to top. Each index is pushed once and popped at most once, so the whole scan is O(n).

Step 1 / 27
Java
1public static int[] nextGreater(int[] arr) {
2 int n = arr.length;
3 int[] result = new int[n];
4 Arrays.fill(result, -1);
5 Deque<Integer> stack = new ArrayDeque<>(); // indices; their values decrease bottom to top
6 for (int i = 0; i < n; i++) {
7 while (!stack.isEmpty() && arr[stack.peek()] < arr[i]) {
8 result[stack.pop()] = arr[i]; // arr[i] answers the popped index
9 }
10 stack.push(i);
11 }
12 return result;
13}

Live state

0 pushes0 pops

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 27 steps
StepLineStateWhat happens
1L5n=8The stack holds indices whose values decrease from bottom to top. Each index is pushed once and popped at most once, so the whole scan is O(n).
2L10i=0Push index 0.
3L7i=1 arr[i]=7 top=07 is the first greater value to the right of index 0.
4L8i=1 popped=0result[0] = 7.
5L10i=1Push index 1.
6L7i=2 arr[i]=3 top=1The stack top survives, so stop popping.
7L10i=2Push index 2.
8L7i=3 arr[i]=5 top=25 is the first greater value to the right of index 2.
9L8i=3 popped=2result[2] = 5.
10L7i=3 arr[i]=5 top=1The stack top survives, so stop popping.
11L10i=3Push index 3.
12L7i=4 arr[i]=9 top=39 is the first greater value to the right of index 3.
13L8i=4 popped=3result[3] = 9.
14L7i=4 arr[i]=9 top=19 is the first greater value to the right of index 1.
15L8i=4 popped=1result[1] = 9.
16L10i=4Push index 4.
17L7i=5 arr[i]=2 top=4The stack top survives, so stop popping.
18L10i=5Push index 5.
19L7i=6 arr[i]=6 top=56 is the first greater value to the right of index 5.
20L8i=6 popped=5result[5] = 6.
21L7i=6 arr[i]=6 top=4The stack top survives, so stop popping.
22L10i=6Push index 6.
23L7i=7 arr[i]=8 top=68 is the first greater value to the right of index 6.
24L8i=7 popped=6result[6] = 8.
25L7i=7 arr[i]=8 top=4The stack top survives, so stop popping.
26L10i=7Push index 7.
27L12return=[7, 9, 5, 9, -1, 6, 8, -1]Indices still on the stack have no greater value to their right, so they keep -1. 8 pushs and 6 pops: O(n).
7.05.2

Next smaller

Flip the comparison: pop while the top is larger than the new value. The stack values increase from bottom to top.

Next-smaller boundaries are what “largest rectangle in a histogram” needs: each bar extends left and right until it meets a smaller bar.

Next smaller element

Interactive · edit the input

Your input

Try

int[] arr · length 8

4
7
3
5
9
2
6
8
01234567
CurrentOn the stackAnswered

stack (bottom→top)

empty

result[]

·0
·1
·2
·3
·4
·5
·6
·7
Setup

The stack holds indices whose values increase from bottom to top. Each index is pushed once and popped at most once, so the whole scan is O(n).

Step 1 / 25
Java
1public static int[] nextSmaller(int[] arr) {
2 int n = arr.length;
3 int[] result = new int[n];
4 Arrays.fill(result, -1);
5 Deque<Integer> stack = new ArrayDeque<>(); // indices; their values increase bottom to top
6 for (int i = 0; i < n; i++) {
7 while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
8 result[stack.pop()] = arr[i]; // arr[i] answers the popped index
9 }
10 stack.push(i);
11 }
12 return result;
13}

Live state

0 pushes0 pops

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 25 steps
StepLineStateWhat happens
1L5n=8The stack holds indices whose values increase from bottom to top. Each index is pushed once and popped at most once, so the whole scan is O(n).
2L10i=0Push index 0.
3L7i=1 arr[i]=7 top=0The stack top survives, so stop popping.
4L10i=1Push index 1.
5L7i=2 arr[i]=3 top=13 is the first smaller value to the right of index 1.
6L8i=2 popped=1result[1] = 3.
7L7i=2 arr[i]=3 top=03 is the first smaller value to the right of index 0.
8L8i=2 popped=0result[0] = 3.
9L10i=2Push index 2.
10L7i=3 arr[i]=5 top=2The stack top survives, so stop popping.
11L10i=3Push index 3.
12L7i=4 arr[i]=9 top=3The stack top survives, so stop popping.
13L10i=4Push index 4.
14L7i=5 arr[i]=2 top=42 is the first smaller value to the right of index 4.
15L8i=5 popped=4result[4] = 2.
16L7i=5 arr[i]=2 top=32 is the first smaller value to the right of index 3.
17L8i=5 popped=3result[3] = 2.
18L7i=5 arr[i]=2 top=22 is the first smaller value to the right of index 2.
19L8i=5 popped=2result[2] = 2.
20L10i=5Push index 5.
21L7i=6 arr[i]=6 top=5The stack top survives, so stop popping.
22L10i=6Push index 6.
23L7i=7 arr[i]=8 top=6The stack top survives, so stop popping.
24L10i=7Push index 7.
25L12return=[3, 3, 2, 2, 2, -1, -1, -1]Indices still on the stack have no smaller value to their right, so they keep -1. 8 pushs and 5 pops: O(n).
7.05.3

Previous greater

For the previous greater element, the answer for index i is already on the stack when i arrives. Pop every value that is less than or equal to arr[i], since i blocks them from being anyone's previous greater, and the remaining top is the answer.

Stock span uses exactly this: the span of day i is the distance back to the previous greater price.

Java
1static int[] stockSpan(int[] price) {
2 int[] span = new int[price.length];
3 Deque<Integer> stack = new ArrayDeque<>();
4 for (int i = 0; i < price.length; i++) {
5 while (!stack.isEmpty() && price[stack.peek()] <= price[i]) stack.pop();
6 span[i] = stack.isEmpty() ? i + 1 : i - stack.peek(); // distance to the previous greater price
7 stack.push(i);
8 }
9 return span;
10}

Previous greater element

Interactive · edit the input

Your input

Try

int[] arr · length 8

4
7
3
5
9
2
6
8
01234567
CurrentOn the stackAnswered

stack (bottom→top)

empty

result[]

·0
·1
·2
·3
·4
·5
·6
·7
Setup

The stack holds indices whose values decrease from bottom to top. Each index is pushed once and popped at most once, so the whole scan is O(n).

Step 1 / 35
Java
1public static int[] previousGreater(int[] arr) {
2 int n = arr.length;
3 int[] result = new int[n];
4 Arrays.fill(result, -1);
5 Deque<Integer> stack = new ArrayDeque<>(); // indices; their values decrease bottom to top
6 for (int i = 0; i < n; i++) {
7 while (!stack.isEmpty() && arr[stack.peek()] <= arr[i]) {
8 stack.pop(); // can never be a previous greater again
9 }
10 if (!stack.isEmpty()) result[i] = arr[stack.peek()];
11 stack.push(i);
12 }
13 return result;
14}

Live state

0 pushes0 pops

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 35 steps
StepLineStateWhat happens
1L5n=8The stack holds indices whose values decrease from bottom to top. Each index is pushed once and popped at most once, so the whole scan is O(n).
2L10i=0 result[i]=-1The stack is empty: nothing before index 0 is greater, so result[0] = -1.
3L11i=0Push index 0.
4L7i=1 arr[i]=7 top=04 is blocked by 7 for every later index, so it can be discarded.
5L8i=1 popped=0Popped index 0.
6L10i=1 result[i]=-1The stack is empty: nothing before index 1 is greater, so result[1] = -1.
7L11i=1Push index 1.
8L7i=2 arr[i]=3 top=1The stack top survives, so stop popping.
9L10i=2 result[i]=7The stack top 7 is the previous greater value for index 2.
10L11i=2Push index 2.
11L7i=3 arr[i]=5 top=23 is blocked by 5 for every later index, so it can be discarded.
12L8i=3 popped=2Popped index 2.
13L7i=3 arr[i]=5 top=1The stack top survives, so stop popping.
14L10i=3 result[i]=7The stack top 7 is the previous greater value for index 3.
15L11i=3Push index 3.
16L7i=4 arr[i]=9 top=35 is blocked by 9 for every later index, so it can be discarded.
17L8i=4 popped=3Popped index 3.
18L7i=4 arr[i]=9 top=17 is blocked by 9 for every later index, so it can be discarded.
19L8i=4 popped=1Popped index 1.
20L10i=4 result[i]=-1The stack is empty: nothing before index 4 is greater, so result[4] = -1.
21L11i=4Push index 4.
22L7i=5 arr[i]=2 top=4The stack top survives, so stop popping.
23L10i=5 result[i]=9The stack top 9 is the previous greater value for index 5.
24L11i=5Push index 5.
25L7i=6 arr[i]=6 top=52 is blocked by 6 for every later index, so it can be discarded.
26L8i=6 popped=5Popped index 5.
27L7i=6 arr[i]=6 top=4The stack top survives, so stop popping.
28L10i=6 result[i]=9The stack top 9 is the previous greater value for index 6.
29L11i=6Push index 6.
30L7i=7 arr[i]=8 top=66 is blocked by 8 for every later index, so it can be discarded.
31L8i=7 popped=6Popped index 6.
32L7i=7 arr[i]=8 top=4The stack top survives, so stop popping.
33L10i=7 result[i]=9The stack top 9 is the previous greater value for index 7.
34L11i=7Push index 7.
35L13return=[-1, -1, 7, 7, -1, 9, 9, 9]8 pushs and 6 pops: O(n).
7.05.4

Monotonic stacks

The general rule: before pushing a new element, pop everything it makes useless. Each index is pushed once and popped at most once, so a full scan is O(n) even though there is a loop inside the loop.

The comparison decides how duplicates behave. With < for next greater, an equal value does not pop, so equal elements are not each other's answer. With <=, they are. Choose based on the problem's definition of “greater”.

QuestionScanPop while top is…Stack order (bottom→top)
Next greaterleft → right< currentdecreasing
Next smallerleft → right> currentincreasing
Previous greaterleft → right<= currentstrictly decreasing
Previous smallerleft → right>= currentstrictly increasing

Previous smaller element

Interactive · edit the input

Your input

Try

int[] arr · length 8

4
7
3
5
9
2
6
8
01234567
CurrentOn the stackAnswered

stack (bottom→top)

empty

result[]

·0
·1
·2
·3
·4
·5
·6
·7
Setup

The stack holds indices whose values increase from bottom to top. Each index is pushed once and popped at most once, so the whole scan is O(n).

Step 1 / 33
Java
1public static int[] previousSmaller(int[] arr) {
2 int n = arr.length;
3 int[] result = new int[n];
4 Arrays.fill(result, -1);
5 Deque<Integer> stack = new ArrayDeque<>(); // indices; their values increase bottom to top
6 for (int i = 0; i < n; i++) {
7 while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
8 stack.pop(); // can never be a previous smaller again
9 }
10 if (!stack.isEmpty()) result[i] = arr[stack.peek()];
11 stack.push(i);
12 }
13 return result;
14}

Live state

0 pushes0 pops

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 33 steps
StepLineStateWhat happens
1L5n=8The stack holds indices whose values increase from bottom to top. Each index is pushed once and popped at most once, so the whole scan is O(n).
2L10i=0 result[i]=-1The stack is empty: nothing before index 0 is smaller, so result[0] = -1.
3L11i=0Push index 0.
4L7i=1 arr[i]=7 top=0The stack top survives, so stop popping.
5L10i=1 result[i]=4The stack top 4 is the previous smaller value for index 1.
6L11i=1Push index 1.
7L7i=2 arr[i]=3 top=17 is blocked by 3 for every later index, so it can be discarded.
8L8i=2 popped=1Popped index 1.
9L7i=2 arr[i]=3 top=04 is blocked by 3 for every later index, so it can be discarded.
10L8i=2 popped=0Popped index 0.
11L10i=2 result[i]=-1The stack is empty: nothing before index 2 is smaller, so result[2] = -1.
12L11i=2Push index 2.
13L7i=3 arr[i]=5 top=2The stack top survives, so stop popping.
14L10i=3 result[i]=3The stack top 3 is the previous smaller value for index 3.
15L11i=3Push index 3.
16L7i=4 arr[i]=9 top=3The stack top survives, so stop popping.
17L10i=4 result[i]=5The stack top 5 is the previous smaller value for index 4.
18L11i=4Push index 4.
19L7i=5 arr[i]=2 top=49 is blocked by 2 for every later index, so it can be discarded.
20L8i=5 popped=4Popped index 4.
21L7i=5 arr[i]=2 top=35 is blocked by 2 for every later index, so it can be discarded.
22L8i=5 popped=3Popped index 3.
23L7i=5 arr[i]=2 top=23 is blocked by 2 for every later index, so it can be discarded.
24L8i=5 popped=2Popped index 2.
25L10i=5 result[i]=-1The stack is empty: nothing before index 5 is smaller, so result[5] = -1.
26L11i=5Push index 5.
27L7i=6 arr[i]=6 top=5The stack top survives, so stop popping.
28L10i=6 result[i]=2The stack top 2 is the previous smaller value for index 6.
29L11i=6Push index 6.
30L7i=7 arr[i]=8 top=6The stack top survives, so stop popping.
31L10i=7 result[i]=6The stack top 6 is the previous smaller value for index 7.
32L11i=7Push index 7.
33L13return=[-1, 4, -1, 3, 5, -1, 2, 6]8 pushs and 5 pops: O(n).
7.05.5

Monotonic queues

A monotonic deque is a monotonic stack that can also drop elements from the front. The front removes elements that have left a window by position; the back removes elements beaten by a newcomer by value.

Use ArrayDeque with peekFirst/pollFirst and peekLast/pollLast. Store indices, not values, so you can tell when the front has left the window.

Java
1Deque<Integer> dq = new ArrayDeque<>(); // indices, values decreasing front to back
2// window leaves: if (dq.peekFirst() <= i - k) dq.pollFirst();
3// newcomer wins: while (!dq.isEmpty() && arr[dq.peekLast()] <= arr[i]) dq.pollLast();
4// join: dq.offerLast(i);
5// maximum: arr[dq.peekFirst()]
7.05.6

Window minimum/maximum

The sliding window maximum keeps a decreasing deque. The front is always the largest value in the current window. Each index enters and leaves the deque once, so all n - k + 1 window maxima take O(n) total, compared with O(n · k) for rescanning every window.

For the window minimum, keep the deque increasing instead. Running both deques at once answers questions like “longest subarray where max − min ≤ limit”.

Worst

O(n)

Space

O(k)

Sliding window maximum (monotonic deque)

Interactive · edit the input

Your input

Try

int[] arr · length 8

1
3
-1
-3
5
3
6
7
01234567
EnteringIn the dequeWindow maximum

deque (front→back)

empty

out[]

empty
Setup

The deque keeps only indices that could still become a window maximum, with their values decreasing. The front is always the current maximum.

Step 1 / 23
Java
1public static int[] maxSlidingWindow(int[] arr, int k) {
2 Deque<Integer> dq = new ArrayDeque<>(); // indices; values decrease front to back
3 int[] out = new int[arr.length - k + 1];
4 for (int i = 0; i < arr.length; i++) {
5 if (!dq.isEmpty() && dq.peekFirst() <= i - k) dq.pollFirst(); // fell out of the window
6 while (!dq.isEmpty() && arr[dq.peekLast()] <= arr[i]) dq.pollLast();
7 dq.offerLast(i);
8 if (i >= k - 1) out[i - k + 1] = arr[dq.peekFirst()];
9 }
10 return out;
11}

Live state

0 deque operations

k

3

No condition evaluated on this step
Execution trace (dry run table)Show 23 steps
StepLineStateWhat happens
1L2k=3The deque keeps only indices that could still become a window maximum, with their values decreasing. The front is always the current maximum.
2L7i=0Append index 0 (1).
3L6i=1 arr[i]=3 removed=01 is older and not larger than 3, so it can never be a maximum again. Remove it from the back.
4L7i=1Append index 1 (3).
5L7i=2Append index 2 (-1).
6L8i=2 max=3Window [0..2] is full: its maximum is the deque front, 3.
7L7i=3Append index 3 (-3).
8L8i=3 max=3Window [1..3] is full: its maximum is the deque front, 3.
9L5i=4 removed=1Index 1 has left the window [2..4], so drop it from the front.
10L6i=4 arr[i]=5 removed=3-3 is older and not larger than 5, so it can never be a maximum again. Remove it from the back.
11L6i=4 arr[i]=5 removed=2-1 is older and not larger than 5, so it can never be a maximum again. Remove it from the back.
12L7i=4Append index 4 (5).
13L8i=4 max=5Window [2..4] is full: its maximum is the deque front, 5.
14L7i=5Append index 5 (3).
15L8i=5 max=5Window [3..5] is full: its maximum is the deque front, 5.
16L6i=6 arr[i]=6 removed=53 is older and not larger than 6, so it can never be a maximum again. Remove it from the back.
17L6i=6 arr[i]=6 removed=45 is older and not larger than 6, so it can never be a maximum again. Remove it from the back.
18L7i=6Append index 6 (6).
19L8i=6 max=6Window [4..6] is full: its maximum is the deque front, 6.
20L6i=7 arr[i]=7 removed=66 is older and not larger than 7, so it can never be a maximum again. Remove it from the back.
21L7i=7Append index 7 (7).
22L8i=7 max=7Window [5..7] is full: its maximum is the deque front, 7.
23L10return=[3, 3, 5, 5, 6, 7]6 windows answered with 15 deque operations: O(n), not O(n · k).

Practice ladder

Now try it yourself

Easy

  • Next greater element I
  • Final prices with a special discount

Medium

  • Daily temperatures
  • Online stock span
  • Sliding window maximum
  • Sum of subarray minimums

Advanced

  • Largest rectangle in histogram
  • Maximal rectangle
  • Longest continuous subarray with absolute diff ≤ limit

Key takeaways

  • Pop everything the new element makes useless, then push it; each index is pushed and popped at most once.
  • Next/previous greater/smaller differ only in scan direction and the comparison.
  • A monotonic deque gives every sliding window maximum or minimum in O(n).