Skip to content

单调栈(Monotonic Stack)

面试官问:"给你一个数组,对每个元素找出它右边第一个比它大的元素。要求 O(n)。"

暴力做法是对每个元素往右遍历,O(n²)。但有一种巧妙的数据结构可以一次遍历搞定——单调栈

原理拆解

单调栈就是一个普通的栈,只是我们保证栈内元素从栈底到栈顶保持单调递增(或递减)。当新元素破坏单调性时,就弹出栈顶元素——而弹出的元素的答案就是当前新元素。

数组 [2, 1, 2, 4, 3],找每个元素右边第一个更大的:

遍历:
  i=0, val=2: 栈空,入栈 → 栈 [2]
  i=1, val=1: 1 < 2(栈顶),入栈 → 栈 [2, 1]
  i=2, val=2: 2 > 1(栈顶),弹出 1 → 1 的右边第一个更大是 2
              2 == 2,入栈 → 栈 [2, 2]
  i=3, val=4: 4 > 2,弹出 2 → 2 的右边第一个更大是 4
              4 > 2,弹出 2 → 2 的右边第一个更大是 4
              栈空,入栈 → 栈 [4]
  i=4, val=3: 3 < 4,入栈 → 栈 [4, 3]

遍历结束,栈中剩余元素没有右边更大的 → 答案为 -1

代码实现

经典问题一:下一个更大元素

LeetCode 739. Daily Temperatures / 496. Next Greater Element

TypeScript

typescript
/**
 * 每日温度 —— TypeScript 实现
 * 单调递减栈:找每个元素右边第一个更大的
 */
function dailyTemperatures(temperatures: number[]): number[] {
  const n = temperatures.length;
  const result = new Array(n).fill(0);
  const stack: number[] = []; // 存索引,栈底到栈顶温度递减

  for (let i = 0; i < n; i++) {
    // 当前温度比栈顶高 → 栈顶找到了"更暖的一天"
    while (
      stack.length > 0 &&
      temperatures[i] > temperatures[stack[stack.length - 1]]
    ) {
      const j = stack.pop()!;
      result[j] = i - j; // 等待天数
    }
    stack.push(i);
  }
  return result;
}

console.log(dailyTemperatures([73, 74, 75, 71, 69, 72, 76, 73]));
// [1, 1, 4, 2, 1, 1, 0, 0]

Go

go
package monotonicstack

// DailyTemperatures 每日温度 —— Go 实现
func DailyTemperatures(temperatures []int) []int {
	n := len(temperatures)
	result := make([]int, n)
	stack := []int{} // 存索引

	for i := 0; i < n; i++ {
		for len(stack) > 0 && temperatures[i] > temperatures[stack[len(stack)-1]] {
			j := stack[len(stack)-1]
			stack = stack[:len(stack)-1]
			result[j] = i - j
		}
		stack = append(stack, i)
	}
	return result
}

Java

java
import java.util.*;

public class DailyTemperatures {
    public static int[] solve(int[] temperatures) {
        int n = temperatures.length;
        int[] result = new int[n];
        Deque<Integer> stack = new ArrayDeque<>();

        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
                int j = stack.pop();
                result[j] = i - j;
            }
            stack.push(i);
        }
        return result;
    }
}

Python

python
"""每日温度 —— Python 实现"""

def daily_temperatures(temperatures: list[int]) -> list[int]:
    n = len(temperatures)
    result = [0] * n
    stack = []  # 存索引

    for i, temp in enumerate(temperatures):
        while stack and temp > temperatures[stack[-1]]:
            j = stack.pop()
            result[j] = i - j
        stack.append(i)
    return result

经典问题二:接雨水

LeetCode 42. Trapping Rain Water

TypeScript

typescript
/**
 * 接雨水 —— TypeScript 实现(单调栈)
 * 栈中存索引,栈底到栈顶高度递减
 */
function trap(height: number[]): number {
  const stack: number[] = [];
  let water = 0;

  for (let i = 0; i < height.length; i++) {
    while (stack.length > 0 && height[i] > height[stack[stack.length - 1]]) {
      const bottom = stack.pop()!; // 凹槽底部
      if (stack.length === 0) break;
      const left = stack[stack.length - 1]; // 左边界
      const width = i - left - 1;
      const h = Math.min(height[i], height[left]) - height[bottom];
      water += width * h;
    }
    stack.push(i);
  }
  return water;
}

console.log(trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])); // 6

Python

python
"""接雨水 —— Python 实现"""

def trap(height: list[int]) -> int:
    stack = []
    water = 0

    for i, h in enumerate(height):
        while stack and h > height[stack[-1]]:
            bottom = stack.pop()
            if not stack:
                break
            left = stack[-1]
            width = i - left - 1
            water += width * (min(h, height[left]) - height[bottom])
        stack.append(i)
    return water

面试题精选

题号题目单调栈思路难度
739每日温度找右边第一个更大中等
496下一个更大元素 I单调栈 + 哈希简单
503下一个更大元素 II环形数组,遍历两遍中等
42接雨水单调栈计算凹槽困难
84柱状图中最大的矩形单调栈找左右边界困难
85最大矩形逐行转化为 84 题困难
901股票价格跨度单调递减栈中等
402移掉 K 位数字单调栈删数字中等

业务场景

1. 股票价格分析

找"某只股票上一次高于当前价格是哪天"——就是单调栈的"上一个更大元素"。技术分析中的支撑位、阻力位计算也用这个思路。

2. 浏览器历史记录

"前进/后退"按钮用栈实现。如果要实现"跳到最近访问的更高级别页面",可以用单调栈。

3. 编译器括号匹配

检查代码中的括号是否匹配——虽然用普通栈就行,但如果要处理嵌套层级的优先级(比如运算符优先级),单调栈就派上用场了。

复杂度分析

操作时间复杂度空间复杂度
全过程O(n)O(n)

每个元素最多入栈一次、出栈一次,所以总时间 O(n)。

小结

单调栈的核心:维护单调性,新元素破坏单调性时弹出的元素就找到了答案

  • 找右边第一个更大 → 单调递减栈
  • 找右边第一个更小 → 单调递增栈
  • 找左边 → 从右往左遍历,或者栈中就是"左边第一个"

口诀:栈内单调别乱加,破坏单调就弹它。弹出元素找答案,右边首个就是它

最近更新