Monotonic Stack
Find the next taller, smaller or bigger item for every element in one O(n) pass by keeping a stack in sorted order, plus the deque version for windows.
On this page
A street has six buildings in a row, with heights of 5, 3, 1, 4, 6 and 2 floors from west to east. For each building, you want the first taller building to its east, the one that blocks its view of the sunrise. Building 1 (3 floors) is blocked by building 3 (4 floors). Building 4 (6 floors) is blocked by nothing.
The obvious way is to start at each building and walk east until you hit a taller one. That is O(n²) in the worst case: on a street that gets steadily shorter, every walk goes to the very end. With 100,000 buildings that is about 5 billion steps. A stack does it in one pass.
The idea: a line of buildings still waiting
Walk from west to east. Keep a stack of the buildings that have not found their answer yet. When a new building arrives, look at the top of the stack. If the new building is taller, it is the answer for that waiting building, so pop it and record the answer. Keep popping while the new building is taller than the top. Then push the new building: it now waits for its own answer.
Anything shorter than the new building is popped before it is pushed. So the heights on the stack always go down from bottom to top. A stack whose items stay in sorted order like this is called a monotonic stack, here a decreasing one.
Popping is safe. The popped building has found its answer, and the answer can only be the first taller building, which is this one. It will never need to be looked at again.
answer = [-1] * nfor i in 0 .. n - 1: while stack not empty and h[top] < h[i]: answer[pop()] = i push(i)return answer- heights051321344652stack (top on the right)answer0-11-12-13-14-15-1
1Every answer starts as -1, meaning "no taller building". The stack is empty. It holds indexes; each cell shows index:height.
- heights05i1321344652stack (top on the right)00:5answer0-11-12-13-14-15-1
2Building 0 (5 floors). Nobody is waiting, so just push it.
- heights0513i21344652stack (top on the right)00:511:3answer0-11-12-13-14-15-1
3Building 1 (3 floors). The top is 5 floors, not shorter, so nothing is answered. Push 1.
- heights051321i344652stack (top on the right)00:511:322:1answer0-11-12-13-14-15-1
4Building 2 (1 floor). Shorter than the top again. Push it. The stack reads 5, 3, 1: decreasing.
- heights05132134i4652stack (top on the right)00:511:3answer0-11-1233-14-15-1
5Building 3 (4 floors). The top, building 2, has 1 floor: shorter. Building 3 is its first taller neighbour. Pop it and record answer[2] = 3.
- heights05132134i4652stack (top on the right)00:5answer0-113233-14-15-1
6The new top, building 1, has 3 floors: still shorter than 4. Pop it, answer[1] = 3.
- heights05132134i4652stack (top on the right)00:513:4answer0-113233-14-15-1
7The top now has 5 floors, taller than 4, so the popping stops. Push building 3. The stack reads 5, 4: still decreasing.
- heights0513213446i52stack (top on the right)00:5answer0-11323344-15-1
8Building 4 (6 floors). The top has 4 floors. Pop it, answer[3] = 4.
- heights0513213446i52stack (top on the right)answer041323344-15-1
9The top has 5 floors, also shorter than 6. Pop it, answer[0] = 4.
- heights0513213446i52stack (top on the right)04:6answer041323344-15-1
10The stack is empty. Push building 4.
- heights051321344652istack (top on the right)04:615:2answer041323344-15-1
11Building 5 (2 floors). Shorter than the top. Push it.
- heights051321344652stack (top on the right)04:615:2answer041323344-15-1
- pushes
- 6
- pops
- 4
12Done. Buildings 4 and 5 are still waiting, so they keep -1. Six pushes and four pops: each building went on the stack once and came off at most once.
The code
// For each building, the index of the first taller one to its right, or -1.
function nextTaller(heights) {
const answer = new Array(heights.length).fill(-1);
const stack = []; // indexes still waiting; heights decrease bottom to top
for (let i = 0; i < heights.length; i++) {
while (stack.length > 0 && heights[stack[stack.length - 1]] < heights[i]) {
answer[stack.pop()] = i; // i is the first taller one for the top
}
stack.push(i); // i waits for its own answer
}
return answer; // anything still waiting keeps -1
}
nextTaller([5, 3, 1, 4, 6, 2]); // [4, 3, 3, 4, -1, -1]# For each building, the index of the first taller one to its right, or -1.
def next_taller(heights):
answer = [-1] * len(heights)
stack = [] # indexes still waiting; heights decrease bottom to top
for i, h in enumerate(heights):
while stack and heights[stack[-1]] < h:
answer[stack.pop()] = i # i is the first taller one for the top
stack.append(i) # i waits for its own answer
return answer # anything still waiting keeps -1
next_taller([5, 3, 1, 4, 6, 2]) # [4, 3, 3, 4, -1, -1]The stack stores indexes, not heights: you need the index to write into answer, and you can look up the height from it. The index also tells you how far away the answer is: i - index.
| Approach | Time | Space | Why |
|---|---|---|---|
| Walk right from every item | O(n²) | O(1) | A steadily falling street makes every walk go to the end. |
| Monotonic stack | O(n) | O(n) | Each index is pushed once and popped at most once: at most 2n stack operations in total. |
The while inside the for looks like O(n²). Count pops instead of loop runs: a pop removes an index for good, and only n indexes are ever pushed, so all the while loops together pop at most n times. This is the same "count the total moves" argument as in Two Pointers.
Four questions, one pattern
Flip the comparison and you get the other variants. For "next smaller", pop while the top is bigger than the new item; the stack is then increasing. For "previous taller" or "previous smaller", read the answer from the other side: after the popping stops, whatever is left on top is the nearest earlier item that survived, so it is the answer for the new item. Here you also pop equal items, so that what is left on top is strictly taller or strictly smaller.
| Question | Pop while the top is… | Who gets the answer |
|---|---|---|
| Next greater | smaller than the new item | each popped item gets the new one |
| Next smaller | bigger than the new item | each popped item gets the new one |
| Previous greater | smaller than or equal to the new item | the new item gets whatever is left on top |
| Previous smaller | bigger than or equal to the new item | the new item gets whatever is left on top |
The deque version: the biggest in every window
A close cousin asks for the largest value in every stretch of k items next to each other. For [4, 2, 12, 3, 8, 7, 1] and k = 3, the windows are [4, 2, 12], [2, 12, 3] and so on, and the answers are 12, 12, 12, 8, 8.
Keep a decreasing stack of indexes exactly as before: a new item pops every item that is not bigger than it off the back, because such an older item can never again be the biggest in any window that contains the new one. The biggest item in the window is then at the front. When the front's index slides out of the window, remove it from the front. Removing from both ends means a deque; the JavaScript version uses the head-index trick from that lesson.
function windowMax(nums, k) {
const dq = []; // indexes; values decrease from front to back
let head = 0; // front of the deque
const result = [];
for (let i = 0; i < nums.length; i++) {
if (head < dq.length && dq[head] <= i - k) head++; // front left the window
while (head < dq.length && nums[dq[dq.length - 1]] <= nums[i]) dq.pop();
dq.push(i);
if (i >= k - 1) result.push(nums[dq[head]]); // front is the biggest
}
return result;
}
windowMax([4, 2, 12, 3, 8, 7, 1], 3); // [12, 12, 12, 8, 8]from collections import deque
def window_max(nums, k):
dq = deque() # indexes; values decrease from front to back
result = []
for i, x in enumerate(nums):
if dq and dq[0] <= i - k:
dq.popleft() # front left the window
while dq and nums[dq[-1]] <= x:
dq.pop()
dq.append(i)
if i >= k - 1:
result.append(nums[dq[0]]) # front is the biggest
return result
window_max([4, 2, 12, 3, 8, 7, 1], 3) # [12, 12, 12, 8, 8]Each index enters the deque once and leaves at most once, from one end or the other, so this is O(n) too, against O(n·k) for scanning every window. The window itself slides as in Sliding Window.
Common mistakes
Check yourself
Run the next-taller pass on [2, 7, 3, 5, 1]. What is the answer array?
The loop has a while inside a for. Why is it O(n) and not O(n²)?
You want the first shorter building to the right of each one. What changes?
What to practise next
Run nextTaller by hand on a falling street, [9, 7, 4, 2], and a rising one, [1, 3, 6, 8], to see the two extremes: a stack that only grows, and one that never holds more than one building. Then solve Waiting for a Bigger Wave with the stack and Freezer Warmest Stretch with the deque. The next module begins with Simple Sorts, where you put whole lists in order.