1️⃣ Core Idea
Pop elements that can no longer contribute to any future answer.

Why Monotonic Stack?
Brute force for “next greater element” = O(n²). Monotonic stack = O(n). Every element is pushed once and popped at most once → 2n operations total.
2️⃣ When to Think Monotonic Stack 🚀
✔️ Strong Signals
- “Next greater element”
- “Previous smaller element”
- “Largest rectangle in histogram”
- “Trapping rain water”
- “Stock span” / “Daily temperatures”
- “Sum of subarray minimums/maximums”
- “Remove k digits” to make smallest number
- Any problem asking about the nearest larger/smaller element in either direction
✔️ Hidden Signals
- Finding boundaries (left boundary, right boundary) for each element
- “How far can element
iextend left/right?” - DP where
A[i] = min(A[j:k]) + C→ monotonic deque variant - “132 pattern” — relationships between three elements
3️⃣ The Four Monotonic Stack Types
| Type | Property | Example |
|---|---|---|
| Strictly Increasing | Each element > previous | [1, 4, 5, 8, 9] |
| Non-decreasing | Each element >= previous | [1, 4, 5, 5, 8] |
| Strictly Decreasing | Each element < previous | [9, 8, 5, 4, 1] |
| Non-increasing | Each element <= previous | [9, 9, 8, 5, 5] |
4️⃣ The Four Templates (Master These!)
Convention
- Stack always stores indices (
st.top()is an index,arr[st.top()]is the value)- Default value:
-1for “no previous element”,nfor “no next element”- Previous → traverse Left → Right (building answer as we go)
- Next → traverse Right → Left (building answer as we go)
The Inverse Rule
Greater elements → use a Decreasing stack (pop smaller ones) Smaller elements → use an Increasing stack (pop larger ones) Think of it as opposites attract.
Template 1 — Next Greater Element (NGE)
// Next Greater Element (strictly greater)
// Stack type: Strictly Decreasing (pop when <= current)
// Traversal: Right → Left | Condition: <=
vector<int> nge(n);
stack<int> st;
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && arr[st.top()] <= arr[i])
st.pop();
nge[i] = st.empty() ? n : st.top();
st.push(i);
}Template 2 — Next Smaller Element (NSE)
// Next Smaller Element (strictly smaller)
// Stack type: Strictly Increasing (pop when >= current)
// Traversal: Right → Left | Condition: >=
vector<int> nse(n);
stack<int> st;
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && arr[st.top()] >= arr[i])
st.pop();
nse[i] = st.empty() ? n : st.top();
st.push(i);
}What’s happening? Going right to left, we pop everything >= current → what remains is strictly smaller → that’s our NSE.
Template 3 — Previous Smaller Element (PSE)
// Previous Smaller Element (strictly smaller)
// Stack type: Strictly Increasing (pop when >= current)
// Traversal: Left → Right | Condition: >=
vector<int> pse(n);
stack<int> st;
for (int i = 0; i < n; i++) {
while (!st.empty() && arr[st.top()] >= arr[i])
st.pop();
pse[i] = st.empty() ? -1 : st.top();
st.push(i);
}What’s happening? We pop everything >= current → what remains on top is strictly smaller → that’s our PSE.
Template 4 — Previous Greater Element (PGE)
// Previous Greater Element (strictly greater)
// Stack type: Strictly Decreasing (pop when <= current)
// Traversal: Left → Right | Condition: <=
vector<int> pge(n);
stack<int> st;
for (int i = 0; i < n; i++) {
while (!st.empty() && arr[st.top()] <= arr[i])
st.pop();
pge[i] = st.empty() ? -1 : st.top();
st.push(i);
}📋 Complete Cheatsheet
| Template | What | Traversal | Condition | Default | Stack Type |
|---|---|---|---|---|---|
| PSE | Prev Smaller | Left → Right | >= | -1 | Strictly Increasing |
| NSE | Next Smaller | Right → Left | >= | n | Strictly Increasing |
| PGE | Prev Greater | Left → Right | <= | -1 | Strictly Decreasing |
| NGE | Next Greater | Right → Left | <= | n | Strictly Decreasing |
🔀 Handling Duplicates — Strict vs Non-Strict
Sometimes you need “next greater or equal” instead of “next strictly greater”. Just flip the operator:
| What you want | Condition in while loop |
|---|---|
| Strictly smaller | >= |
| Smaller or equal | > |
| Strictly greater | <= |
| Greater or equal | < |
Duplicate Handling in Problems like "Sum of Subarray Mins"
When duplicates exist, use one strict and one non-strict boundary to avoid double counting. Example: PSE uses
>=(strict) and NSE uses>(non-strict), or vice versa.
🔄 Single-Pass Merged Template (PSE + NSE Together) ( IGNORE THIS POINT )
You can compute both Previous Smaller and Next Smaller in a single left-to-right pass:
// Single-pass: PSE (strictly smaller) + NSE (smaller or equal)
// Note: one is strict, the other is non-strict — handles duplicates
vector<int> pse(n, -1), nse(n, n);
stack<int> st;
for (int i = 0; i < n; i++) {
while (!st.empty() && arr[st.top()] >= arr[i]) {
nse[st.top()] = i; // current element is NSE for popped element
st.pop();
}
pse[i] = st.empty() ? -1 : st.top(); // stack top is PSE for current
st.push(i);
}Strict vs Non-Strict in Merged Template
With
>=: PSE is strictly smaller, but NSE becomes smaller or equal (not strict). With>: PSE is smaller or equal, but NSE becomes strictly smaller. Pick based on what the problem needs. For most problems (like histogram), either works as long as you’re consistent.
Similarly for PGE + NGE merged:
// Single-pass: PGE (strictly greater) + NGE (greater or equal)
vector<int> pge(n, -1), nge(n, n);
stack<int> st;
for (int i = 0; i < n; i++) {
while (!st.empty() && arr[st.top()] <= arr[i]) {
nge[st.top()] = i;
st.pop();
}
pge[i] = st.empty() ? -1 : st.top();
st.push(i);
}5️⃣ Pattern 1 — Direct NGE / NSE Problems
These problems directly ask for the next/previous greater/smaller element.
Next Greater Element I — LC 496 ⭐
Given two arrays nums1 (subset of nums2), find the next greater element for each element of nums1 in nums2.
// LC 496. Next Greater Element I
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
int n = nums2.size();
unordered_map<int, int> nge;
stack<int> st;
// NGE template on nums2 (Right → Left)
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && st.top() <= nums2[i])
st.pop();
nge[nums2[i]] = st.empty() ? -1 : st.top();
st.push(nums2[i]); // push values since we need a map
}
vector<int> res;
for (int& x : nums1)
res.push_back(nge[x]);
return res;
}
// TC - O(n + m) | SC - O(n)Next Greater Element II (Circular) — LC 503 🔥
Circular array → the next greater of the last element could be the first element!
Trick: Iterate the array twice (2n iterations), use i % n for circular indexing.
// LC 503. Next Greater Element II
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> nge(n, -1);
stack<int> st;
// Two passes: Right → Left, doubled for circular
for (int i = 2 * n - 1; i >= 0; i--) {
while (!st.empty() && nums[st.top()] <= nums[i % n])
st.pop();
if (i < n)
nge[i] = st.empty() ? -1 : st.top();
st.push(i % n);
}
// Convert indices to values
for (int i = 0; i < n; i++)
nge[i] = nge[i] == -1 ? -1 : nums[nge[i]];
return nge;
}
// TC - O(n) | SC - O(n)Daily Temperatures — LC 739 🔥
“How many days until a warmer temperature?” = Next Greater Element in disguise, answer is nge[i] - i.
// LC 739. Daily Temperatures
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> res(n, 0);
stack<int> st;
// NGE template (Right → Left)
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && temperatures[st.top()] <= temperatures[i])
st.pop();
res[i] = st.empty() ? 0 : st.top() - i;
st.push(i);
}
return res;
}
// TC - O(n) | SC - O(n)Online Stock Span — LC 901 🔥
“How many consecutive days the price was ≤ today?” = Previous Greater Element (strictly greater).
Span = i - pge[i].
// LC 901. Online Stock Span
class StockSpanner {
stack<pair<int, int>> st; // {price, index}
int idx = 0;
public:
StockSpanner() {}
int next(int price) {
// PGE template (Left → Right, pop when <=)
while (!st.empty() && st.top().first <= price)
st.pop();
int span = st.empty() ? idx + 1 : idx - st.top().second;
st.push({price, idx});
idx++;
return span;
}
};
// TC - O(1) amortized per call | SC - O(n)6️⃣ Pattern 2 — Boundary Width (Histogram + Rain Water)
These problems use the PSE + NSE combo to find how far each bar can extend.
Key Insight
For each bar of height
h[i], the width it can extend =nse[i] - pse[i] - 1(between its left and right smaller boundaries) Area =h[i] * width
Largest Rectangle in Histogram — LC 84 🔥 📍
The classic monotonic stack problem. For each bar, find PSE and NSE, then compute area.
// LC 84. Largest Rectangle in Histogram
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
vector<int> pse(n, -1), nse(n, n);
stack<int> st;
// Single-pass: PSE + NSE merged
for (int i = 0; i < n; i++) {
while (!st.empty() && heights[st.top()] >= heights[i]) {
nse[st.top()] = i;
st.pop();
}
pse[i] = st.empty() ? -1 : st.top();
st.push(i);
}
int maxArea = 0;
for (int i = 0; i < n; i++) {
int width = nse[i] - pse[i] - 1;
maxArea = max(maxArea, heights[i] * width);
}
return maxArea;
}
// TC - O(n) | SC - O(n)Maximal Rectangle — LC 85 💀
Convert 2D matrix to histogram per row, then apply LC 84 on each row.
// LC 85. Maximal Rectangle
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
vector<int> pse(n, -1), nse(n, n);
stack<int> st;
for (int i = 0; i < n; i++) {
while (!st.empty() && heights[st.top()] >= heights[i]) {
nse[st.top()] = i;
st.pop();
}
pse[i] = st.empty() ? -1 : st.top();
st.push(i);
}
int maxArea = 0;
for (int i = 0; i < n; i++) {
int width = nse[i] - pse[i] - 1;
maxArea = max(maxArea, heights[i] * width);
}
return maxArea;
}
int maximalRectangle(vector<vector<char>>& matrix) {
if (matrix.empty()) return 0;
int rows = matrix.size(), cols = matrix[0].size();
vector<int> heights(cols, 0);
int maxArea = 0;
for (int r = 0; r < rows; r++) {
// Build histogram: if '1', add to height; if '0', reset to 0
for (int c = 0; c < cols; c++)
heights[c] = (matrix[r][c] == '1') ? heights[c] + 1 : 0;
maxArea = max(maxArea, largestRectangleArea(heights));
}
return maxArea;
}
// TC - O(rows * cols) | SC - O(cols)Trapping Rain Water — LC 42 🔥 📍
Monotonic stack approach: Use a decreasing stack. When we find a taller bar, we can trap water on the popped bar using PGE (left wall) and current bar (right wall).
// LC 42. Trapping Rain Water (Monotonic Stack approach)
int trap(vector<int>& height) {
int n = height.size();
stack<int> st;
int water = 0;
for (int i = 0; i < n; i++) {
while (!st.empty() && height[st.top()] <= height[i]) {
int mid = st.top();
st.pop();
if (!st.empty()) {
int h = min(height[st.top()], height[i]) - height[mid];
int w = i - st.top() - 1;
water += h * w;
}
}
st.push(i);
}
return water;
}
// TC - O(n) | SC - O(n)Alternative Approaches
This problem can also be solved with:
- Two pointers — O(n) time, O(1) space (optimal)
- Prefix max arrays — O(n) time, O(n) space The monotonic stack approach is included here as it’s a great exercise in understanding the pattern.
7️⃣ Pattern 3 — Sum of Subarray Mins / Maxs
These problems ask: “For every subarray, find the min (or max), and sum them all.”
Key Insight
Instead of iterating over all subarrays (O(n²)), ask: “How many subarrays have
arr[i]as their minimum?” Answer:left[i] * right[i]where:
left[i]= distance fromito its PSE (or start) =i - pse[i]right[i]= distance fromito its NSE (or end) =nse[i] - iContribution of
arr[i]=arr[i] * left[i] * right[i]Total = Σarr[i] * left[i] * right[i]
Sum of Subarray Minimums — LC 907 🔥 📍
// LC 907. Sum of Subarray Minimums
int sumSubarrayMins(vector<int>& arr) {
int n = arr.size();
const int MOD = 1e9 + 7;
vector<int> pse(n, -1), nse(n, n);
stack<int> st;
// PSE: strictly smaller (>=) → Left → Right
for (int i = 0; i < n; i++) {
while (!st.empty() && arr[st.top()] >= arr[i])
st.pop();
pse[i] = st.empty() ? -1 : st.top();
st.push(i);
}
while (!st.empty()) st.pop();
// NSE: smaller or equal (>) → Right → Left
// Using > (non-strict) to avoid double counting duplicates
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && arr[st.top()] > arr[i])
st.pop();
nse[i] = st.empty() ? n : st.top();
st.push(i);
}
long long ans = 0;
for (int i = 0; i < n; i++) {
long long left = i - pse[i];
long long right = nse[i] - i;
ans = (ans + (long long)arr[i] * left % MOD * right % MOD) % MOD;
}
return (int)ans;
}
// TC - O(n) | SC - O(n)Duplicate Handling
Use
>=for PSE (strictly smaller) and>for NSE (smaller or equal), or vice versa. This ensures each subarray’s minimum is counted exactly once. If both are strict (>=), subarrays where duplicates are the minimum get counted multiple times.
Sum of Subarray Maximums — Variant of LC 907
Same idea, but use PGE + NGE instead. Flip the operators.
// Sum of Subarray Maximums (variant)
long long sumSubarrayMaxs(vector<int>& arr) {
int n = arr.size();
const int MOD = 1e9 + 7;
vector<int> pge(n, -1), nge(n, n);
stack<int> st;
// PGE: strictly greater (<=) → Left → Right
for (int i = 0; i < n; i++) {
while (!st.empty() && arr[st.top()] <= arr[i])
st.pop();
pge[i] = st.empty() ? -1 : st.top();
st.push(i);
}
while (!st.empty()) st.pop();
// NGE: greater or equal (<) → Right → Left
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && arr[st.top()] < arr[i])
st.pop();
nge[i] = st.empty() ? n : st.top();
st.push(i);
}
long long ans = 0;
for (int i = 0; i < n; i++) {
long long left = i - pge[i];
long long right = nge[i] - i;
ans = (ans + (long long)arr[i] * left % MOD * right % MOD) % MOD;
}
return ans;
}Sum of Subarray Ranges — LC 2104 🔥
Sum of Subarray Ranges = Sum of Subarray Maximums − Sum of Subarray Minimums
// LC 2104. Sum of Subarray Ranges
long long subArrayRanges(vector<int>& nums) {
// Answer = sumOfSubarrayMaxs - sumOfSubarrayMins
// Use the two functions above
return sumSubarrayMaxs(nums) - sumSubarrayMins(nums);
}
// TC - O(n) | SC - O(n)8️⃣ Pattern 4 — Stack-Based Construction / Greedy
These problems use monotonic stacks to build an optimal sequence by deciding what to keep and what to discard.
Remove K Digits — LC 402 🔥
Make the smallest number by removing k digits. Use an increasing stack — pop larger digits when a smaller one arrives.
// LC 402. Remove K Digits
string removeKdigits(string num, int k) {
string st; // using string as stack for convenience
for (char& c : num) {
// Increasing stack: pop when top > current (and we still have removals left)
while (!st.empty() && st.back() > c && k > 0) {
st.pop_back();
k--;
}
st.push_back(c);
}
// If k > 0, remove from the end (stack is increasing, largest are at end)
while (k > 0) {
st.pop_back();
k--;
}
// Remove leading zeros
int start = 0;
while (start < st.size() && st[start] == '0') start++;
string res = st.substr(start);
return res.empty() ? "0" : res;
}
// TC - O(n) | SC - O(n)Remove Duplicate Letters — LC 316 💀
Return the smallest subsequence with all unique characters. Monotonic increasing stack + frequency counting.
// LC 316. Remove Duplicate Letters
string removeDuplicateLetters(string s) {
vector<int> freq(26, 0);
vector<bool> inStack(26, false);
string st;
for (char& c : s) freq[c - 'a']++;
for (char& c : s) {
freq[c - 'a']--;
if (inStack[c - 'a']) continue;
// Increasing stack: pop if top > current AND top appears later
while (!st.empty() && st.back() > c && freq[st.back() - 'a'] > 0) {
inStack[st.back() - 'a'] = false;
st.pop_back();
}
st.push_back(c);
inStack[c - 'a'] = true;
}
return st;
}
// TC - O(n) | SC - O(1) — at most 26 chars in stack9️⃣ Pattern 5 — Tricky / Advanced
132 Pattern — LC 456 💀
Find i < j < k with nums[i] < nums[k] < nums[j] (1-3-2 pattern).
Approach: Traverse right to left. Maintain a decreasing stack for potential “3” (nums[j]). Track the largest popped value as “2” (nums[k]). If current < “2”, we found “1”.
// LC 456. 132 Pattern
bool find132pattern(vector<int>& nums) {
int n = nums.size();
stack<int> st; // decreasing stack — candidates for "3" (the largest)
int second = INT_MIN; // the "2" in 1-3-2 (largest value popped)
// Right → Left
for (int i = n - 1; i >= 0; i--) {
// If current < second, we found "1" < "2" < "3" ✓
if (nums[i] < second) return true;
// Pop elements smaller than current → they become candidates for "2"
while (!st.empty() && nums[st.top()] < nums[i]) {
second = max(second, nums[st.top()]);
st.pop();
}
st.push(i);
}
return false;
}
// TC - O(n) | SC - O(n)Why right to left?
We want to maximize “2” (nums[k]) so that it’s easier to find a “1” (nums[i]) smaller than it. Going right to left lets us build up “3” candidates and track the best “2” popped so far.
Number of Visible People in a Queue — LC 1944 💀
Person i can see person j (j > i) if everyone between them is shorter than both.
Approach: Right to left, decreasing stack. When popping (person is shorter than current), current can see them. After popping, if stack is non-empty, current can also see the stack top (the next taller person).
// LC 1944. Number of Visible People in a Queue
vector<int> canSeePersonsCount(vector<int>& heights) {
int n = heights.size();
vector<int> res(n, 0);
stack<int> st;
// Right → Left, strictly decreasing stack
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && heights[st.top()] < heights[i]) {
st.pop();
res[i]++; // can see each person we pop
}
if (!st.empty())
res[i]++; // can also see the next taller/equal person
st.push(i);
}
return res;
}
// TC - O(n) | SC - O(n)Buildings With an Ocean View — LC 1762 ⭐
Ocean is to the right. A building has a view if all buildings to its right are shorter.
Approach: Right to left, strictly decreasing stack. At the end, everything left in the stack has an ocean view.
// LC 1762. Buildings With an Ocean View
vector<int> findBuildings(vector<int>& heights) {
int n = heights.size();
stack<int> st;
for (int i = n - 1; i >= 0; i--) {
// Pop buildings that are shorter or equal
while (!st.empty() && heights[st.top()] < heights[i])
st.pop();
// Only push if this building is taller than all to its right
if (st.empty() || heights[i] > heights[st.top()])
st.push(i);
}
// Stack has indices in decreasing order, reverse for increasing order
vector<int> res;
while (!st.empty()) {
res.push_back(st.top());
st.pop();
}
reverse(res.begin(), res.end());
return res;
}
// TC - O(n) | SC - O(n)Sliding Window Maximum — LC 239 💀
Find the maximum in every sliding window of size k. Uses a monotonic deque (decreasing).
// LC 239. Sliding Window Maximum
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
deque<int> dq; // decreasing deque of indices
vector<int> res;
for (int i = 0; i < n; i++) {
// Remove elements outside the window
while (!dq.empty() && dq.front() <= i - k)
dq.pop_front();
// Maintain decreasing order: pop smaller elements from back
while (!dq.empty() && nums[dq.back()] <= nums[i])
dq.pop_back();
dq.push_back(i);
// Window is complete, record the max (front of deque)
if (i >= k - 1)
res.push_back(nums[dq.front()]);
}
return res;
}
// TC - O(n) | SC - O(k)Monotonic Deque vs Stack
This is a deque (double-ended queue), not a stack. We need
pop_frontto expire old elements. The back of the deque works like a monotonic stack. The front gives us the window maximum.
🧠 Master Cheatsheet
Pattern Recognition
| Signal in problem | Pattern to use |
|---|---|
| ”Next greater / smaller element” | Direct NGE/NSE template |
| ”Daily temperatures” / “Stock span” | Direct NGE/PGE (in disguise) |
| “Largest rectangle” / “Maximal rectangle” | PSE + NSE → width × height |
| ”Trapping rain water” | Decreasing stack, process on pop |
| ”Sum of subarray minimums / maximums” | PSE + NSE → contribution counting |
| ”Remove k digits” / “Smallest subsequence” | Increasing stack + greedy |
| ”132 pattern” | Decreasing stack, track popped max |
| ”Sliding window maximum” | Monotonic deque (decreasing) |
| “Visible people in queue” | Decreasing stack, count pops |
The Decision Tree
Need nearest greater/smaller element?
├── Previous → traverse Left → Right, answer from stack top after popping
├── Next → traverse Right → Left, answer from stack top after popping
│
├── Greater → pop when stack top <= current (decreasing stack)
└── Smaller → pop when stack top >= current (increasing stack)
Need both PSE + NSE (or PGE + NGE)?
└── Single-pass merged template: assign NSE inside while, PSE outside
└── ⚠️ One will be strict, one non-strict — fine for most problems
Duplicates causing double counting?
└── Use one strict (>=) and one non-strict (>) boundary
Common Gotchas
1. Store INDICES in stack, not values → gives you position info for free
2. Default for "previous" = -1, for "next" = n (not -1!)
3. Duplicates: if both PSE and NSE are strict (>=), you'll double count
4. Circular array: iterate 2n times, use i % n
5. The condition in while loop decides the stack type:
>= creates strictly increasing, > creates non-decreasing
<= creates strictly decreasing, < creates non-increasing