代码随想录算法训练营Day-48 单调栈02 | 42. 接雨水、84.柱状图中最大的矩形

42. 接雨水

双指针方法

思路:

按照列来求,每一列的雨水量,是:自己做底hi,左边比自己高的柱子lefti和右边比自己高的柱子righti的最小值再减去底,因此为max(lefti,righti)-hi;

所以只需得到left和right数组;下面讲解left和right得到的逻辑。

left数组第一个元素初始化为h0,因为第一个柱子左边没柱子,自己就是最高的,然后从左向右遍历,lefti就是比左边一个柱子高的柱子和自己高度之间的最大值(逻辑是:如果左边柱子的left也就是lefti-1比自己高,那么自己的left也就是lefti就等于lefti-1;如果不比自己高,那么自己就是最高的,那么lefti就是自己的高度heighti,因此取二者最大值即可);

right数组倒数第一个元素初始化为hh.size()-1,因为右边第一个柱子的右边没柱子,自己就是最高的,然后从右向左遍历,righti就是比右边一个柱子高的柱子和自己高度之间的最大值(逻辑是:如果右边柱子的right也就是righti+1比自己高,那么自己的right也就是righti就等于righti+1;如果不比自己高,那么自己就是最高的,那么righti就是自己的高度heighti,因此取二者最大值即可);

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        vector<int> maxLeft(n,0);
        vector<int> maxRight(n,0);
        maxLeft[0] = height[0];
        maxRight[n-1] = height[n-1];
        for(int i = 1;i<n;i++) maxLeft[i] = max(maxLeft[i-1],height[i]);
        for(int j = n-2;j>=0;j--) maxRight[j] = max(maxRight[j+1],height[j]);

        int result = 0;
        for(int k =1;k<n-1;k++){
            int water = min(maxLeft[k],maxRight[k])-height[k];
            if(water>0) result+=water;
        }

        return result;
    } 
};

单调栈方法

思路:

重点是如何用单调栈获取左边的高柱和右边的高柱。其实单调递增栈,当当前元素比栈顶元素大的时候,就是"栈顶元素为底,当前元素为右边高柱,栈顶元素的下一个元素为左边高柱"的情形。

所以只需要在当前元素大于栈顶元素时,执行以下逻辑:

如果栈不为空,且当前元素大于栈顶元素:

获取栈顶元素为mid,把栈顶元素弹出;

再获取栈顶元素(左边高柱),取左右高柱最小值-hmid,得到雨水深度,再乘以雨水宽度(i-栈顶元素(索引,也就是左边高柱的索引)-1);

(此处解释一下,为什么乘以宽度?这里不是按列求的了吗?每列宽度不就是1吗? 其实用单调栈就不是按列求的了,因为单调栈会在当前元素等于栈顶元素时,把当前元素也入栈,所以会出现栈内有多个连续相同的值,也就是非严格递增 。所以就相当于一个非严格抬升的台阶,在遇到严格抬升的地方的时候,前面可能已经有好几个连续没抬升的地方了,那些地方的储水量都是0,因为没抬升就没有储水高度,但当遍历到平地的最后一个元素(后面有一个严格高柱),它作为底时储水量为左右高柱最小值-hmid,再乘以雨水宽度(平地宽度),其实也就相当于把这一片的水一次性加上了。所以这里其实是按行计算

所以过程可以总结为:

柱子下降时入栈,柱子上升时出栈并计算储水量,遇到平柱(左高柱等于底高)不储水,遇到高柱,一次性储完前面平地的水。

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> st;
        st.push(0);
        int result = 0;
        for(int i=1;i<height.size();i++){
            if(height[i]<=height[st.top()]) st.push(i);
            else{
                while(!st.empty() && height[i]>height[st.top()]){
                    int mid = st.top();
                    st.pop();
                    if(!st.empty()){
                        result+=(min(height[i],height[st.top()])-height[mid])*(i-st.top()-1);
                    }
                }
                st.push(i);
            }

        }
        return result;
    } 
};

84.柱状图中最大的矩形

思路:

目标是:对每一根柱子,找到它左右两边第一个比它矮的柱子,然后以当前柱子的高度作为矩形高度,计算它能扩展出的最大宽度;

所以本题使用递减单调栈,当遇到当前元素大于等于栈顶元素的情况执行入栈;

小于当前元素的时候在进行计算操作。

小于当前元素是,说明遇到了以栈顶元素为高度,当前元素为右矮柱,栈顶下一个元素为左矮柱的情况,此时可以切出一个高为栈顶元素,宽为左右矮柱索引之差-1的一个矩形;

为什么左右要补0,且不需要判断栈为空?

左补0,是为了避免栈弹空,导致元素漏算的情况,比如3,2,1,一开始3入栈,然后到2,又要把3弹出,此时栈就空了,但漏掉了1*3这个矩形;

右补0,是为了避免一直入栈,无法弹出,导致漏算,比如1,2,3,一直入栈,没遇到比栈顶元素小的元素,所以无法计算面积,所以漏掉了1*3这个矩形。

所以左右补0是为了补充左右边界,防止漏算的。

cpp 复制代码
class Solution {
public:
    int largestRectangleArea(vector<int>& height) {
        stack<int> st;
        st.push(0);
        int result = 0;
        for(int i=1;i<height.size();i++){
            if(height[i]>=height[st.top()]) st.push(i);
            else{
                while(!st.empty() && height[i]<height[st.top()]){
                    int mid = st.top();
                    st.pop();
                    if(!st.empty()){
                        result=max(min(height[i],height[st.top()])*(i-st.top()-1),result);
                    }
                }
                st.push(i);
            }

        }
        return result;
    }
};

相关推荐
Mortalbreeze14 小时前
C++11 ---- 右值引用、值类型
开发语言·c++
少司府14 小时前
C++进阶:多态
c语言·开发语言·c++·多态·抽象类·虚函数·虚表指针
并不喜欢吃鱼14 小时前
从零开始 C++----- 十三【C++ 数据结构】哈希表从原理到手撕实现(开放定址 + 链地址全覆盖)
数据结构·c++·散列表
愿天垂怜14 小时前
【C++脚手架】etcd 的介绍与使用
java·linux·服务器·c语言·c++·中间件·etcd
_Oracle15 小时前
机器学习——常见算法
人工智能·算法·机器学习
x_xbx15 小时前
LeetCode:17. 电话号码的字母组合
算法·leetcode·职场和发展
小则又沐风a15 小时前
进程篇: 进程概念的补充(了解环境变量和虚拟地址空间)
linux·运维·服务器·c++
郝学胜-神的一滴15 小时前
[简化版 GAMES 101] 计算机图形学 11:频域·卷积·抗锯齿
c++·unity·图形渲染·opengl·three·unreal
山楂树の15 小时前
广度优先搜索 (BFS)
算法·广度优先·宽度优先
valan liya15 小时前
C++ 继承
开发语言·c++