单调栈知识点

单调栈 = 保持栈内元素单调递增或单调递减的栈。

专门解决「找左边/右边第一个比当前大/小的元素」这类问题。


看到以下特征,直接想单调栈:

  1. 找下一个更大元素(Next Greater Element)
  2. 找下一个更小元素(Next Smaller Element)
  3. 找前一个更大/更小元素
  4. 柱状图中最大矩形、接雨水 这类经典题

时间复杂度: O(n),每个元素入栈出栈各一次。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 输入 nums,返回每个元素右边第一个比它大的元素值,没有返回 -1
vector<int> nextGreaterElement(vector<int>& nums) {
    int n = nums.size();
    vector<int> res(n, -1);
    stack<int> st;  // 存下标,不是值

    // 从后往前遍历
    for (int i = n - 1; i >= 0; i--) {
        // 栈顶比当前小,肯定不是答案,弹出
        while (!st.empty() && nums[st.top()] <= nums[i]) {
            st.pop();
        }
        // 此时栈顶就是右边第一个更大的
        if (!st.empty()) res[i] = nums[st.top()];
        st.push(i);
    }
    return res;
}
目标遍历方向弹出条件单调性
右边第一个更大从后往前<= 当前栈底→栈顶 递减
右边第一个更小从后往前>= 当前栈底→栈顶 递增
左边第一个更大从前往后<= 当前栈底→栈顶 递减
左边第一个更小从前往后>= 当前栈底→栈顶 递增

口诀:找大就弹小,找小就弹大


找右边第一个更高温度的天数差。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
vector<int> dailyTemperatures(vector<int>& temperatures) {
    int n = temperatures.size();
    vector<int> res(n, 0);
    stack<int> st;

    for (int i = n - 1; i >= 0; i--) {
        while (!st.empty() && temperatures[st.top()] <= temperatures[i]) {
            st.pop();
        }
        if (!st.empty()) res[i] = st.top() - i;  // 存下标,算天数差
        st.push(i);
    }
    return res;
}

数组是环形的,把数组翻倍(遍历 2n 次,下标取模)即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
vector<int> nextGreaterElements(vector<int>& nums) {
    int n = nums.size();
    vector<int> res(n, -1);
    stack<int> st;

    for (int i = 2 * n - 1; i >= 0; i--) {
        while (!st.empty() && st.top() <= nums[i % n]) {
            st.pop();
        }
        if (i < n && !st.empty()) res[i] = st.top();
        st.push(nums[i % n]);
    }
    return res;
}

经典中的经典,核心:对每个柱子,找左右第一个比它矮的,宽度 = 右-左-1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int largestRectangleArea(vector<int>& heights) {
    int n = heights.size();
    vector<int> left(n, -1);   // 左边第一个更小的下标
    vector<int> right(n, n);   // 右边第一个更小的下标
    stack<int> st;

    // 找左边第一个更小
    for (int i = 0; i < n; i++) {
        while (!st.empty() && heights[st.top()] >= heights[i]) {
            st.pop();
        }
        if (!st.empty()) left[i] = st.top();
        st.push(i);
    }

    // 清空栈,找右边第一个更小
    while (!st.empty()) st.pop();
    for (int i = n - 1; i >= 0; i--) {
        while (!st.empty() && heights[st.top()] >= heights[i]) {
            st.pop();
        }
        if (!st.empty()) right[i] = st.top();
        st.push(i);
    }

    // 计算面积
    int ans = 0;
    for (int i = 0; i < n; i++) {
        ans = max(ans, heights[i] * (right[i] - left[i] - 1));
    }
    return ans;
}

也可以用单调栈做(也有双指针解法)。


  1. 判断类型:找大还是找小?左边还是右边?
  2. 确定遍历方向:找右边从后往前,找左边从前往后
  3. 确定弹出条件:找大弹小(<=),找小弹大(>=)
  4. 栈存什么:通常存下标(方便算距离),也可以存值
  5. 初始化答案:默认 -1 或 0 或 n

  1. 等号要不要加?

    • 严格更大:用 <= 弹出(相等的也弹掉)
    • 大于等于:用 < 弹出
    • 看题目要求,「更大」还是「大于等于」
  2. 存下标还是存值?

    • 需要算距离/索引 → 存下标
    • 只需要值 → 存值也可以
  3. 循环数组:遍历 2n 次,下标取模 n

  4. 栈为空时:说明没有符合条件的元素,答案为 -1(或边界值)


题目类型难度
496. 下一个更大元素 I模板题简单
739. 每日温度模板题中等
503. 下一个更大元素 II循环数组中等
84. 柱状图中最大的矩形经典难题困难
42. 接雨水多种方法困难
901. 股票价格跨度左边第一个更大中等
316. 去除重复字母单调栈+贪心中等
402. 移掉 K 位数字单调栈+贪心中等