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

42. 接雨水

双指针方法

思路:

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

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

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

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

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,把栈顶元素弹出;

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

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

所以过程可以总结为:

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

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;
    }
};

相关推荐
Hcoco_me1 小时前
Ai:Agent/ infra / 智驾 / 推广算法 题库
人工智能·深度学习·算法·自动驾驶·剪枝
项目申报小狂人1 小时前
提出了一种带双向搜索的粒子群优化算法,一种基于双四元数运动优化的新型无人机3D路径规划方法及应用
算法·3d·无人机
驼同学.1 小时前
牛客网面试TOP101 - Python算法学习指南
python·算法·面试
大大杰哥1 小时前
Java集合框架(List/Set/Queue)核心总结与代码示例
java·数据结构
大大杰哥1 小时前
leetcode hot100(3)子串
c++·算法·leetcode
fish_xk1 小时前
哈希的了解
算法·哈希算法
水木流年追梦1 小时前
大模型入门-应用篇1-prompt技术
开发语言·python·算法·prompt
莫生灬灬1 小时前
ElementUI封装 共91个组件 支持易语言/火山/C#/Python
开发语言·c++·python·ui·elementui·c#
WL_Aurora1 小时前
【每日一题】位运算
python·算法