代码随想录算法训练营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;
    }
};

相关推荐
kisshyshy1 小时前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
众少成多积小致巨3 小时前
JNI (Java Native Interface) 技术手册中文参考指南
android·java·c++
猿人谷8 小时前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络10 小时前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络10 小时前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao40010 小时前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao40010 小时前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法
JieE2122 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
JieE2123 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack203 天前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法