🗂️Data StructureIntermediate

Monotonic Stack

Key Points

  • A monotonic stack is a stack that keeps its elements in increasing or decreasing order to answer range queries in linear time.
  • It solves classic problems like Next Greater/Smaller Element, Previous Greater/Smaller Element, Largest Rectangle in Histogram, and Trapping Rain Water.
  • The key insight is amortized O(n) work: each index is pushed and popped at most once.
  • Store indices, not values, so you can compute widths, distances, and access original values efficiently.
  • Choose increasing vs. decreasing and strict vs. non-strict comparisons carefully to handle duplicates correctly.
  • Use sentinels (virtual bars of height 0) or an extra loop to flush the stack cleanly at the end.
  • Monotonic stacks generalize to matrices (maximal rectangle) by treating each row as a histogram.
  • They are simple to implement, memory-light (O(n)), and very common in coding interviews and competitive programming.

Prerequisites

  • Arrays and indexingMonotonic stacks operate over arrays and require index manipulation for distances and widths.
  • Stack data structureUnderstanding push/pop, top, and LIFO behavior is essential to implement the pattern.
  • Big-O and amortized analysisTo justify O(n) performance and reason about while-pop loops.
  • Basic integer arithmeticUsed for computing widths, areas, and sums safely.

Detailed Explanation

Tap terms for definitions

01Overview

Imagine scanning a skyline of bars and wanting to know, for each bar, where the next taller or shorter bar is. Doing this naively for every bar would be slow. A monotonic stack is a clever structure that keeps just the right candidates in a stack so that when a new bar arrives, you can quickly discard the ones that are no longer useful. The stack stays sorted (monotonically increasing or decreasing) with respect to the values you care about. This lets you answer queries like "next greater element" in one pass. Hook → Concept → Example: Hook: You want to compute, for each day, the next warmer day from a list of temperatures. Concept: Maintain a decreasing stack of indices of temperatures; when a warmer day arrives, it resolves the waiting colder days on the stack. Example: If temps are [73, 74, 75, 71, 69, 72, 76, 73], each index is pushed once and popped at most once, so the whole process is linear. In practice, the monotonic stack pattern shows up in numerous array and grid problems: largest rectangle in histogram, maximal rectangle in a binary matrix, and trapping rain water. The implementation is compact, typically a loop with a while-pop and a push, and often uses indices to compute distances or widths efficiently.

02Intuition & Analogies

Think of a checkout line where people are arranged by height. Suppose the rule is: keep the line strictly decreasing in height from back to front. When a taller person arrives, they force all shorter people at the back to step out until the rule is satisfied. Each person steps out at most once. This is exactly how a decreasing monotonic stack behaves: when a larger value arrives, it pops all smaller values that it invalidates. Another analogy: Consider sorting homework by due dates but only keeping the ones that might be due before the next assignment arrives. As new assignments come in, you remove those that can never be the next due date. The pile stays ordered, and you never re-add an assignment you removed. For histogram area, picture placing a rectangle that extends as far left and right as possible while staying under a certain height. The nearest smaller bars to the left and right block the expansion; a monotonic increasing stack tracks those blockers automatically. For water trapping, imagine pouring water from above. Valleys trap water between walls; the stack tracks rising walls and computes trapped volume whenever a new right wall closes a basin. The magic is amortization: although the while loop can pop multiple items at once, each item is pushed once and popped once, so across the whole array the total number of pops is bounded by the number of pushes. That’s why the total time is linear.

03Formal Definition

A monotonic stack is a stack data structure that maintains its elements in a specified monotonic order based on a key function, typically array values at stored indices. For an array a[0..n-1], an increasing monotonic stack keeps indices i such that a[] < a[] < ... < a[] from bottom to top (strict) or a[] a[] ... a[] (non-strict). A decreasing monotonic stack uses the reverse inequality. The canonical algorithm scans the array once from left to right. At index i, it repeatedly pops from the stack while the monotonicity condition is violated by a[i]; each popped index j yields an answer such as the next greater/smaller element at i or a computed width using the new top as a boundary. Then it pushes i onto the stack. Optionally, a final cleanup phase pops remaining indices to produce answers (e.g., next element does not exist) or to compute leftover areas. Amortized analysis: if each index is pushed once and popped at most once, the total number of stack operations over the entire pass is O(n). Memory usage is O(n) in the worst case (e.g., strictly increasing sequence for a decreasing stack), since up to n indices can be stored simultaneously.

04When to Use

Use a monotonic stack when you need nearest-neighbor relationships constrained by order, such as nearest greater/smaller elements to the left or right. Classic cases include:

  • Next Greater/Smaller Element: stock span, daily temperatures, and resolving dependencies to the first qualifying element to the right.
  • Previous Greater/Smaller Element: computing spans or left boundaries for segments.
  • Largest Rectangle in Histogram: for each bar, find the nearest smaller to left/right to get the maximal width, then take the maximum area.
  • Maximal Rectangle in a Binary Matrix: treat each row as the base of a histogram with heights of consecutive ones; apply the histogram algorithm row by row.
  • Trapping Rain Water: track rising walls to compute water trapped in basins efficiently. Often, if a brute-force nested loop seems to be “for each i, scan left/right until you find the first element that beats a condition,” a monotonic stack can reduce it to O(n). Choose increasing vs. decreasing carefully based on whether you look for greater or smaller, and pick strict vs. non-strict inequalities to handle duplicates according to the problem’s requirement.

⚠️Common Mistakes

  1. Using values instead of indices when distances/widths are needed. Always store indices so you can compute widths (r - l - 1) and access values a[i].
  2. Wrong inequality direction. For Next Greater Element, use a decreasing stack (pop while a[i] > a[st.top()]); for Next Smaller Element, use an increasing stack (pop while a[i] < a[st.top()]).
  3. Mishandling duplicates. Decide whether equal elements should pop or stay. For strict next greater, keep equals on the stack (use <= or >= accordingly); for next greater or equal, pop equals too.
  4. Forgetting to flush the stack. Many algorithms need a final loop (or a sentinel) to process elements that never found a blocker on the right.
  5. Off-by-one in widths. When popping index mid, left boundary is st.empty() ? 0 : st.top() + 1, and right boundary is current index i - 1. Width is i - left.
  6. Not guarding empty stack. Always check st.empty() before accessing st.top().
  7. Overflow with large products. For histogram area, cast to long long if heights and widths can be large.
  8. Inefficient repeated work. Don’t rescan left or right explicitly; let the stack’s amortization guarantee O(n).

Key Formulas

Amortized Stack Operations

Explanation: Across a single pass, each index is pushed once and popped at most once, so total stack operations are linear. This yields overall O(n) time even if individual iterations pop many elements.

Next Greater Element Definition

Explanation: For each position i, the next greater element is the earliest index to the right with a strictly larger value. If no such index exists, it is undefined (or set to -1 in implementations).

Histogram Rectangle Area

Explanation: For bar i of height , is the index of the previous smaller bar and is the index of the next smaller bar. The rectangle extends between them excluding boundaries, so width is - - 1.

Per-Index Trapped Water

Explanation: At position i, and are the highest bars to the left and right. Water level is the smaller of these walls; subtract the current height to get water depth (never negative).

Time and Space Complexity

Explanation: Scanning the array once with a monotonic stack yields linear time and linear auxiliary space in the worst case. The constants are small because each element is processed a constant number of times.

Maximal Rectangle Complexity

Explanation: For a matrix with R rows and C columns, updating histogram heights per row and applying the O(C) histogram algorithm per row yields total time proportional to R times C.

Complexity Analysis

The core efficiency of monotonic stacks comes from amortized analysis. During a single left-to-right pass over n elements, each index is pushed at most once onto the stack. Popping only occurs when a new element invalidates one or more top elements under the monotonic condition, but any specific index can be popped at most once. Therefore, the total number of push and pop operations is at most 2n, leading to O(n) time overall. Even though a single iteration’s inner while loop may pop multiple elements, these pops are “paid for” by their prior pushes. Space complexity is O(n) in the worst case because the stack may hold many indices simultaneously (e.g., a strictly increasing array for a decreasing stack). However, the stack size is always bounded by the number of processed elements, and constants are typically small. For specific applications: Next Greater/Smaller Element runs in O(n) time, O(n) space. Largest Rectangle in Histogram runs in O(n) time, O(n) space by computing maximal width for each bar using nearest smaller boundaries. Trapping Rain Water via stack also runs in O(n) time, O(n) space by identifying basins when a right wall appears. Maximal Rectangle in a Binary Matrix transforms each row into a histogram of heights and applies the histogram algorithm per row, yielding O(R·C) time and O(C) extra space (beyond input). Careful handling of duplicates and sentinels ensures correctness without increasing asymptotic costs.

Code Examples

Next Greater Element (Decreasing Stack of Indices)
1#include <bits/stdc++.h>
2using namespace std;
3
4// Compute Next Greater Element for each index. If none exists, answer is -1.
5// Time: O(n), Space: O(n)
6vector<int> nextGreaterElement(const vector<int>& a) {
7 int n = (int)a.size();
8 vector<int> ans(n, -1);
9 stack<int> st; // stack of indices, values strictly decreasing on stack
10 for (int i = 0; i < n; ++i) {
11 // Resolve all indices whose next greater is a[i]
12 while (!st.empty() && a[i] > a[st.top()]) {
13 ans[st.top()] = a[i];
14 st.pop();
15 }
16 st.push(i);
17 }
18 // Remaining indices have no next greater; already -1
19 return ans;
20}
21
22int main() {
23 ios::sync_with_stdio(false);
24 cin.tie(nullptr);
25
26 vector<int> a = {2, 1, 2, 4, 3};
27 vector<int> nge = nextGreaterElement(a);
28
29 cout << "Array: ";
30 for (int x : a) cout << x << ' ';
31 cout << "\nNext Greater: ";
32 for (int x : nge) cout << x << ' ';
33 cout << "\n";
34 return 0;
35}
36

We maintain a decreasing stack of indices so that the top is the most recent unresolved index with a larger value than any below it. When a[i] arrives, it is the next greater element for all smaller values at the top; we pop them and set their answer to a[i]. Indices that remain on the stack have no greater element to the right.

Time: O(n)Space: O(n)
Largest Rectangle in Histogram (Increasing Stack + Sentinel)
1#include <bits/stdc++.h>
2using namespace std;
3
4// Compute the largest rectangle in a histogram using a monotonic increasing stack.
5// Time: O(n), Space: O(n)
6long long largestRectangleArea(vector<int> h) {
7 // Append a sentinel 0 height to flush the stack at the end
8 h.push_back(0);
9 int n = (int)h.size();
10 stack<int> st; // indices with increasing heights
11 long long best = 0;
12 for (int i = 0; i < n; ++i) {
13 // Maintain increasing stack; when current is lower, resolve areas
14 while (!st.empty() && h[i] < h[st.top()]) {
15 int mid = st.top(); st.pop();
16 long long height = h[mid];
17 // After popping mid, the new top is the previous smaller to the left
18 int leftBoundary = st.empty() ? 0 : st.top() + 1; // first index where height >= current height becomes left boundary + 1
19 int rightBoundaryExclusive = i; // current i is the first smaller to the right
20 long long width = rightBoundaryExclusive - leftBoundary;
21 best = max(best, height * width);
22 }
23 st.push(i);
24 }
25 return best;
26}
27
28int main() {
29 ios::sync_with_stdio(false);
30 cin.tie(nullptr);
31
32 vector<int> h = {2, 1, 5, 6, 2, 3};
33 cout << largestRectangleArea(h) << "\n"; // Expected: 10 (bars 5 and 6)
34 return 0;
35}
36

The stack holds indices of bars with strictly increasing heights. When we encounter a lower bar, we repeatedly pop bars and compute the maximal rectangle in which the popped bar is the limiting height. The left boundary is one past the new stack top (previous smaller), and the right boundary is the current index (next smaller). Appending a 0 height forces all remaining bars to be processed.

Time: O(n)Space: O(n)
Trapping Rain Water (Decreasing Stack to Close Basins)
1#include <bits/stdc++.h>
2using namespace std;
3
4// Compute total trapped water using a monotonic decreasing stack of indices.
5// Time: O(n), Space: O(n)
6long long trap(const vector<int>& h) {
7 int n = (int)h.size();
8 stack<int> st; // decreasing heights: h[st[0]] >= h[st[1]] >= ...
9 long long water = 0;
10 for (int i = 0; i < n; ++i) {
11 // When we find a right wall higher than the basin bottom, close basins
12 while (!st.empty() && h[i] > h[st.top()]) {
13 int bottom = st.top(); st.pop();
14 // If no left wall, cannot trap water yet
15 if (st.empty()) break;
16 int left = st.top();
17 int width = i - left - 1; // distance between walls excluding them
18 int boundedHeight = min(h[left], h[i]) - h[bottom];
19 if (boundedHeight > 0) water += 1LL * width * boundedHeight;
20 }
21 st.push(i);
22 }
23 return water;
24}
25
26int main() {
27 ios::sync_with_stdio(false);
28 cin.tie(nullptr);
29
30 vector<int> h = {0,1,0,2,1,0,1,3,2,1,2,1};
31 cout << trap(h) << "\n"; // Expected: 6
32 return 0;
33}
34

Maintain a decreasing stack of heights' indices. When a higher right wall arrives, pop the basin bottom and compute trapped water between the left wall (new stack top) and the current right wall. The width is the horizontal distance between walls minus one, and the bounded height is the smaller wall minus the basin bottom.

Time: O(n)Space: O(n)
Maximal Rectangle in a Binary Matrix via Row-wise Histograms
1#include <bits/stdc++.h>
2using namespace std;
3
4// Largest rectangle in histogram helper (same as previous example, slightly refactored)
5long long largestRectangleArea(const vector<int>& h) {
6 vector<int> a = h;
7 a.push_back(0);
8 stack<int> st;
9 long long best = 0;
10 for (int i = 0; i < (int)a.size(); ++i) {
11 while (!st.empty() && a[i] < a[st.top()]) {
12 int mid = st.top(); st.pop();
13 long long height = a[mid];
14 int left = st.empty() ? 0 : st.top() + 1;
15 int width = i - left;
16 best = max(best, height * 1LL * width);
17 }
18 st.push(i);
19 }
20 return best;
21}
22
23// Maximal rectangle of 1s in a binary matrix using histogram heights per row.
24// Time: O(R*C), Space: O(C)
25long long maximalRectangle(const vector<string>& grid) {
26 if (grid.empty()) return 0;
27 int R = (int)grid.size();
28 int C = (int)grid[0].size();
29 vector<int> height(C, 0);
30 long long ans = 0;
31 for (int r = 0; r < R; ++r) {
32 for (int c = 0; c < C; ++c) {
33 if (grid[r][c] == '1') height[c] += 1; else height[c] = 0;
34 }
35 ans = max(ans, largestRectangleArea(height));
36 }
37 return ans;
38}
39
40int main() {
41 ios::sync_with_stdio(false);
42 cin.tie(nullptr);
43
44 vector<string> grid = {
45 "10100",
46 "10111",
47 "11111",
48 "10010"
49 };
50 cout << maximalRectangle(grid) << "\n"; // Expected: 6
51 return 0;
52}
53

Treat each row as the base of a histogram where height[c] counts consecutive 1s above. For each row, compute the largest rectangle in that histogram using the monotonic increasing stack. The maximum over all rows is the maximal rectangle of 1s in the matrix.

Time: O(R*C)Space: O(C)