栈:单调栈

2021-08-17 每日一题 LeetCode

# 0042. 接雨水 (opens new window)

题目

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

解析

使用单调栈存储高度下标,按照行方向来计算雨水容量。

具体维护的顺序为从栈顶到栈底的高度有小到大,因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈顶元素就是凹槽底部的柱子,栈顶第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。这样通过左右柱子就可以计算长和宽得到雨水的容量了。

代码

class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        stack<int> stk;
        for (int i = 0; i < height.size(); i++) {
            while (!stk.empty() && height[i] >= height[stk.top()]) {
                int idx = stk.top(); stk.pop();
                if (stk.empty()) break;

                int w = i - stk.top() - 1;
                int h = min(height[i], height[stk.top()]) - height[idx];
                
                ans += w * h;
            }
            stk.push(i);
        }
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 0084. 柱状图中最大的矩形 (opens new window)

题目

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

解析

本题与 0042. 接雨水 解法相似,但由于计算面积需要找到当前柱子左右第一个比它小的柱子,因此栈内顺序与「接雨水」相反,即:从栈顶到栈底的高度右大到小,这样就可以得到当前高度的左右边界,遍历所有高度即可求出最大值。

代码

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> stk;
        int ans = 0;
        heights.push_back(-1);
        for (int i = 0; i < heights.size(); i++) {
            while (!stk.empty() && heights[i] < heights[stk.top()]) {
                int idx = stk.top(); stk.pop();
                int left = stk.empty() ? -1 : stk.top();
                ans = max(ans, (i - left - 1) * heights[idx]);
            }
            stk.push(i);
        }
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 0085. 最大矩形 (opens new window)

题目

给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

解析

本题是 0084. 柱状图中最大的矩形 的改编版,只需在原有基础上对每一行作为 x 轴的柱状图求解即可。

代码

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        vector<int> heights(n);
        int ans = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == '1') heights[j]++;
                else heights[j] = 0;
            }
            ans = max(ans, maxArea(heights));
        }
        return ans;
    }

    int maxArea(vector<int>& heights) {
        stack<int> stk;
        int ans = 0;
        heights.push_back(-1);
        for (int i = 0; i < heights.size(); i++) {
            while (!stk.empty() && heights[i] < heights[stk.top()]) {
                int idx = stk.top(); stk.pop();
                int left = stk.empty() ? -1 : stk.top();
                ans = max(ans, (i - left - 1) * heights[idx]);
            }
            stk.push(i);
        }
        heights.pop_back();
        return ans;
    }
};
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

# 0316. 去除重复字母 (opens new window)

题目

给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

解析

本题需要维护一个保留原始相对顺序的去重单调栈,相对顺序的控制可以通过预先统计的每个字母出现次数来实现。

代码

class Solution {
public:
    string removeDuplicateLetters(string s) {
        vector<int> cnt(256);
        for (int i = 0; i < s.length(); i++) {
            cnt[s[i]]++;
        }

        stack<char> stk;
        vector<bool> inStk(256);
        for (int i = 0; i < s.length(); i++) {
            cnt[s[i]]--;

            // 去重
            if (inStk[s[i]]) continue;

            // 维护单调栈
            while (!stk.empty() && s[i] < stk.top()) {
                // 若后续不存在当前字符则结束
                if (cnt[stk.top()] == 0) break;
                
                inStk[stk.top()] = false;
                stk.pop();
            }
            stk.push(s[i]);
            inStk[s[i]] = true;
        }

        string ans = "";
        while (!stk.empty()) {
            ans = stk.top() + ans;
            stk.pop();
        }
        return ans;
    }
};
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
33
34
35
36

# 0402. 移掉K位数字 (opens new window)

题目

给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。

解析

由于相同位数的数字,靠近左侧的数字越小则整体值越小,因此可以维护一个单调栈,每遇到一个较小的值都将之前入栈的元素取出,这样在取出同样位数的数字之后,保证了左侧的数字递增顺序。

有以下几个特殊情况需要处理:

  1. 单调栈生成之后栈内元素数超过 n - k(即:原始数字从高到低位就是近似递增的),这样需要把多余的数字 pop 出来;
  2. 返回结果前需要剔除前导 0(如:201,剔除 1 位后,栈内元素为 01);
  3. 最终结果需要对空字符串强制转 0。

代码

class Solution {
public:
    string removeKdigits(string num, int k) {
        stack<char> stk;
        for (int i = 0; i < num.length(); i++) {
            while (k > 0 && !stk.empty() && num[i] < stk.top()) {
                stk.pop();
                k--;
            }
            stk.push(num[i]);
        }
        // 若单调栈中元素个数超过 n - k,则进行 pop
        while (k--) {
            stk.pop();
        }

        string ans = "";
        while (!stk.empty()) {
            ans = stk.top() + ans;
            stk.pop();
        }
        
        // 去除前导 0
        ans = ans.erase(0, ans.find_first_not_of('0'));

        return ans.empty() ? "0" : ans;
    }
};
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

# 0456. 132 模式 (opens new window)

题目

给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。

如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。

解析

以中间数字 k 为基准,从后向前维护一个单调递减栈,这样确保已经找到了有效的 (j,k),同时需要记录遍历过程中 k 的最大值,这样 i 的可选范围才更大,当遇到有效的 i 时直接返回即可。

代码

class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        int k = INT_MIN;
        stack<int> stk;
        for (int i = nums.size() - 1; i >= 0; i--) {
            if (nums[i] < k) return true;
            while (!stk.empty() && nums[i] > stk.top()) {
                k = stk.top();
                stk.pop();
            }
            stk.push(nums[i]);
        }
        return false;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 0496. 下一个更大元素 I (opens new window)

题目

给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。

请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。

nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

解析

维护一个单调栈,记录每次遇到比栈顶元素大的值,注意只记录第一个即可。

代码

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        unordered_map<int, int> record;
        stack<int> stk;
        for (int i = 0; i < nums2.size(); i++) {
            while (!stk.empty() && nums2[i] > nums2[stk.top()]) {
                // 只记录第一个最大的,已记录过则跳过
                if (record.find(nums2[stk.top()]) == record.end()) {
                    record[nums2[stk.top()]] = nums2[i];
                }
                stk.pop();
            }
            stk.push(i);
        }
        vector<int> ans(nums1.size(), -1);
        for (int i = 0; i < nums1.size(); i++) {
            if (record.find(nums1[i]) != record.end()) {
                ans[i] = record[nums1[i]];
            }
        }
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 0503. 下一个更大元素 II (opens new window)

题目

给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

解析

单调栈基础题,需要注意的是,这里用取模操作模拟了循环数组的遍历。

代码

class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        vector<int> ans(nums.size(), -1);
        stack<int> stk;
        for (int i = 0; i < nums.size() * 2; i++) {
            while (!stk.empty() && nums[i % nums.size()] > nums[stk.top()]) {
                ans[stk.top()] = nums[i % nums.size()];
                stk.pop();
            }
            stk.push(i % nums.size());
        }
        return ans;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Last Updated: 2023-01-28 4:31:25