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

相关推荐
hhb_6182 分钟前
AIRAGDebug:一键定位RAG链路异常
人工智能·python·算法
朝朝辞暮i19 分钟前
C++ 第 6 课:switch —— 多状态选择
开发语言·c++·算法
朝朝辞暮i43 分钟前
C++ 第 4 课:if / else —— 让程序自己做决定
开发语言·c++
苏打豆1 小时前
Pinocchio源码阅读——刚体运动物理量空间变换及求导
c++·线性代数·算法·矩阵·机器人
m0_380743871 小时前
Qt中导航栏实现的详细指南
开发语言·c++
FluxArt1 小时前
GPT Image 2.5 中文提示词教程:Flux Art 可复用模板
前端·gpt·算法
估值探索者1 小时前
【Python量化策略实战 #02】多因子合成mom和vol两因子打分合成
开发语言·c++·python·数据挖掘·c#
思麟呀1 小时前
位运算:从一个整数到一个集合
c语言·数据结构·c++·算法
阜阳山君GEO2 小时前
豆包 SEED 新版本 GEO 优化:信源金字塔、打分体系与避坑落地指南
人工智能·算法
晓天衡宇•评测社区2 小时前
晓天衡宇亮相 2026 云栖大会:迈向更可信的高质量评测
人工智能·算法·机器学习