单调栈的详解+图解以及leetcode经典矩形面积和接雨水的详解

单调栈的目的主要是寻找数组中某一个位置的 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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值