一、什么时候适合用单调栈
题目要求"为每个元素找左边/右边第一个比它大(或小)的元素",且暴力 O(N²) 会超时 → 用单调栈。这是最核心的信号。看到"第一个更大 / 第一个更小 / 下一个更大 / 下一个更小"这类字眼,基本就是单调栈。
二、典型特征清单
满足以下任意一条,就该考虑单调栈:
| 特征 | 说明 |
|---|---|
| ① 找"第一个更 X" | 左边/右边第一个更大、更小、更高、更矮、更暖…… |
| ② 求"边界区间" | 每个元素能向左/右扩展多远(如最大矩形宽度) |
| ③ 需要"下一个更大/小元素" | Next Greater / Smaller Element 系列 |
| ④ 数组 + 线性时间要求 | 暴力 O(N²) 不行,想要 O(N) |
| ⑤ 元素之间有"遮挡"关系 | 后面的元素让前面的"失效"(如接雨水、柱状图) |
三、方向与单调性的对应表
这是最实用的速查表,决定你用递增栈还是递减栈、从左扫还是从右扫:
| 要找什么 | 遍历方向 | 栈的单调性 |
|---|---|---|
| 左边第一个更小 | 从左往右 | 递增栈(栈底小→栈顶大) |
| 左边第一个更大 | 从左往右 | 递减栈 |
| 右边第一个更小 | 从右往左 | 递增栈 |
| 右边第一个更大 | 从右往左 | 递减栈 |
记法:
找"更小" → 递增栈;找"更大" → 递减栈
找"右边" → 从右往左扫
("每日温度"是找右边第一个更大 → 从右往左扫、递减栈,只不过它常用从左扫的写法,效果一样。)
四、代码参考
1、找左边第一个更小
// 从左到有维护递增栈 // 如果左边没有更小的表示为-1 public int[] findLM(int[] nums) { int n = nums.length; Deque<Integer> stack = new ArrayDeque<>(); int[] ret = new int[n]; for(int i = 0; i < n; i++) { while(!stack.isEmpty() && nums[stack.peek()] >= nums[i]) { stack.pop(); } ret[i] = stack.isEmpty() ? -1 : stack.peek(); stack.push(i); } return ret; }2、找左边第一个更大
// 从左到右维护递减栈 // 如果左边没有更大的表示为-1 public int[] findLMax(int[] nums) { int n = nums.length; Deque<Integer> stack = new ArrayDeque<>(); int[] ret = new int[n]; for(int i = 0; i < n; i++) { while(!stack.isEmpty() && nums[stack.peek()] <= nums[i]) { stack.pop(); } ret[i] = stack.isEmpty() ? -1 : stack.peek(); stack.push(i); } return ret; }3、找右边第一个最小
// 从右到左维护递增栈 // 如果左边没有更小的表示为n public int[] findRMin(int[] nums) { int n = nums.length; Deque<Integer> stack = new ArrayDeque<>(); int[] ret = new int[n]; for(int i = n-1; i >= 0; i--) { while(!stack.isEmpty() && nums[stack.peek()] >= nums[i]) { stack.pop(); } ret[i] = stack.isEmpty() ? n : stack.peek(); stack.push(i); } return ret; }4、找右边第一个最大
// 从右到左维护递减栈 // 如果左边没有更大的表示为n public int[] findRMax(int[] nums) { int n = nums.length; Deque<Integer> stack = new ArrayDeque<>(); int[] ret = new int[n]; for(int i = n-1; i >= 0; i--) { while(!stack.isEmpty() && nums[stack.peek()] <= nums[i]) { stack.pop(); } ret[i] = stack.isEmpty() ? n : stack.peek(); stack.push(i); } return ret; }