单调栈的目的主要是寻找数组中某一个位置的 1、左边第一个比当前元素小(大)的位置 2、右边第一个比当前元素小(大)的位置。
那么是什么原理呢?我们以找比当前元素小为例:

也就是说,当我们制作一个单调递增的栈的时候:
while(!stk.empty() && height[stk.top()] >= height[x]) {
r_first_min[stk.top()] = x;//x右边第一个小于等于height[x]的位置
}
l_first_min[x] = stk.top();//x左边第一个小于height[x]的位置
注意:上面的代码可以看出来左边小于和右边小于等于不可兼得。
反过来也一样,也就是说单调增栈找最小,单调减栈找最大:
例题:最大矩形面积
题意:寻找各个柱子围成的最大面积。
最简单的暴力思路:枚举柱子hi作为高,设L为hi在左边第一个小于它的位置,R为hi的右边第一个小于它的位置,那么当前面积就为:hi * ((r - 1) - (l + 1) + 1 ) = hi * (r - l - 1);
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
vector<int> r_first_min(n, n), l_first_min(n, -1);
stack<int> stk;
for (int i = 0; i < n; i++) {
while(!stk.empty() && heights[stk.top()] >= heights[i]) {
r_first_min[stk.top()] = i;
stk.pop();
}
l_first_min[i] = stk.empty() ? -1 : stk.top();
stk.push(i);
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans = max(ans, heights[i] * (r_first_min[i] - l_first_min[i] - 1));
}
return ans;
}
};
例题二:接雨水

由图可得:对于当前柱子hi,设当前柱子左边最大的柱子高度为l_max,右边最大的柱子高度为r_max,则如果min(l_max, r_max) <= hi,则当前柱子接不到雨水,否则当前柱子能接的雨水为min(l_max, r_max) - hi。
所以我们的思路出来了,求每个柱子左边最大的柱子高度和右边最大的柱子高度。
做法一:动态规划
最简单的我们就是动态规划了:dp_l[i]为第i根柱子左边的最大柱子高度,则dp[i] = max(dp[i - 1], height[i - 1]),同理求dp_r[i]也是一样的:
const int maxn = 1e5 + 50;
int dp_l[maxn], dp_r[maxn];
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
if (n <= 2) return 0;
dp_l[0] = dp_r[n - 1] = -1;
for (int i = 1; i < n; i++) dp_l[i] = max(dp_l[i - 1], height[i - 1]);
for (int i = n - 2; ~i; i--) dp_r[i] = max(dp_r[i + 1], height[i + 1]);
int ans = 0;
for (int i = 0; i < n; i++) {
if (min(dp_l[i], dp_r[i]) >= height[i]) {
ans += min(dp_l[i], dp_r[i]) - height[i];
}
}
return ans;
}
};
做法二:单调栈
上面的做法是一列一列的算面积,而单调栈是一行一行的算面积。
最简单的一行一行的思路是:枚举高度h,然后遍历高度,如果当前高度小于h,且当前高度的左侧和右侧都有大于等于h的存在,那么这个地方就一定有水。
由此可知,结合下面的图我们发现,每一行水都是由当前柱子和这个柱子的右边第一个大于等于当前柱子所组成的,这不就是单调栈了吗?

我们创造一个单调递减的栈,设当前位置是i,则栈里至少存在两个元素,才能接雨水(很明显长度小于等于2的是无法接到雨水的),则设当前top为栈顶元素,left为栈的第二个元素。则按行求的面积的宽为i - left - 1,高度为min(height[left], height[i] - left,面积就出来了。
代码:
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
if (n <= 2) return 0;
stack<int> stk;
int ans = 0;
for (int i = 0; i < n; i++) {
while(!stk.empty() && height[stk.top()] <= height[i]) {
int x = stk.top();
stk.pop();
if (stk.empty()) break;
int left = stk.top();
ans += (min(height[left], height[i]) - height[x]) * (i - 1 - left);
}
stk.push(i);
}
return ans;
}
};
493

被折叠的 条评论
为什么被折叠?



