单调栈(C++)

单调栈,即栈中元素是单调递增的或是单调递减的,是一个比较好用的数据结构.

柱状图中最大的矩形

84. 柱状图中最大的矩形 - 力扣(LeetCode)

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

在这里插入图片描述

​ 注意到,当从左向右看,并尝试计算面积时,总是在前一个比后一个大的时候才会尝试计算, 如果柱子高度是单增的,那么显然会有更大的面积,只有变矮时,才不会右更大的面积.

​ 那么,我们可以创建一个单增的栈,里面存储各个柱的序号,如果后续时单增的,就一直入栈,如果当前元素比栈顶元素小,就出栈,并计算面积.这样只需要一次遍历就可以计算得出.

在这里插入图片描述

(画红虚线的为最终栈中元素)

​ 注意边界问题,在遍历一次柱状图后,栈中可能还会有剩余元素.显然它们是单增的,那么从右往左看,直到栈中的前一个元素,都是可计算面积的.即

	length = res.top();
	res.pop();//pop后的栈顶元素即为约束
	width = res.empty()?n:n-res.top()-1;//已经出栈,则宽度多算了1,为空的话,则说明这个元素是最小的.

完整代码:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        unsigned long n = heights.size();
        if (n == 1){
            return heights[0];
        }
        stack<int> res;
        int ans=0;
        
        //使用一个单增的栈,一遇到单减,立马出栈,计算面积
        for(int i=0;i<n;i++){
            while(!res.empty()&&heights[res.top()]>heights[i]){
                int length=heights[res.top()];
                res.pop();//使用后立即出栈,良好习惯
                int width = res.empty()?i:i-res.top()-1;//注意已经出栈,宽度比实际面积大1,手动减去
                ans = max(ans,length*width);
            }
            res.push(i);
        }
        //完成后可能栈非空,再来一次
        while(!res.empty()){
            int length=heights[res.top()];
            res.pop();
            int width =res.empty()?n : n -res.top()-1;
            ans = max(ans,length*width);
        }
        return ans;
    }
};

接雨水

42. 接雨水 - 力扣(LeetCode)

给定 n个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

在这里插入图片描述

​ 注意到当柱子单减时,突然变大了,则可以接雨水,可以使用单减栈(注意接雨水需要墙,而边界不算墙),判断是否能接水,至少需要三根柱子: c u r , l e f t , r i g h t cur,left,right cur,left,right.

c u r cur cur表示判断可能可以接水的地方,即他的下一个柱子即 r i g h t right right,是比它高的,那么认为, l e f t ∼ r i g h t left\sim right leftright这片区域(不含边界,是开的)的高度均为cur,为什么可以这样认为后续会进一步讲解.那么这三根柱子带来的雨水收益为: ( m i n ( l e f t , r i g h t ) − h e i g h t [ c u r ] ) ∗ ( r i g h t − l e f t − 1 ) ) (min(left,right)-height[cur])*(right-left-1)) (min(left,right)height[cur])(rightleft1))

​ 现在解释为什么可以认为 l e f t ∼ r i g h t left\sim right leftright这片区域可认为高度均为cur:

  • 显然这片区域没有比 c u r cur cur更高的柱子
  • 如果高度均为 h e i g h t [ c u r ] height[cur] height[cur],那么显然成立
  • 如果有比 c u r cur cur低的柱子 p p p,那么在区间 [ c u r , r i g h t ] [cur,right] [cur,right],可认为高度均为 p p p,已经被计算过雨水面积为 ( m i n ( c u r , r i g h t ) − p ) ∗ ( r i g h t − c u r − 1 ) (min(cur,right)-p)*(right-cur-1) (min(cur,right)p)(rightcur1),也就是说那些实际比cur低的但在此次计算中被认为是柱子的空白部分,是被计算过了的.

​ 这是一个递归的过程,如果不太能理解,可以看例子中的序号3到6这一区间的计算过程,你会发现在计算序号5的柱子接水时,加且仅加了1,在后续的计算中,这一方格被自动地认为是柱子,不再认为是空白.

class Solution {
public:
    int trap(vector<int>& height) {
    //注意到,只要单减时遇到变大,就可以接水,考虑单调栈
    stack<int> st;
    int res=0;
    int len=height.size();
    for(int i=0;i<len;i++){
        while(!st.empty()&&height[st.top()]<height[i]){
            int cur=st.top();
            st.pop();
            if(st.empty())
                break;//由于接水需要左右两边都有墙,如果弹出后为空,说明左边没墙,结束,否则溢出
            int l=st.top();
            int r=i;
            int h=min(height[l],height[r])-height[cur];
            res+=(r-l-1)*h;
        }
        st.push(i);
    }
    return res;
    }
};

最大矩形

85. 最大矩形 - 力扣(LeetCode)

给定一个仅包含 0 和 1 、大小为 $rows \times cols $ 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

在这里插入图片描述

​ 注意到这是一个二维的单减栈(最小栈)问题,即每一行都用单调栈处理一次即可.

​ 相当于是每一行都是一个柱状图,而柱状图高度取决于有多少个连续的1,使用height[cols]来维护.

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        if(matrix.empty())
            return 0;
        int rows=matrix.size();
        int cols=matrix[0].size();
        int maxarea=0;
        vector<int> height(cols,0);
        //对每一行都进行计数,并通过最小栈计算出每一行最大的矩形
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(matrix[i][j]=='1')
                    height[j]++;
                else height[j]=0;
            }//注意一列中必须有连续的1才能++,否则就更新为0
            stack<int> st;
            for(int j=0;j<cols;j++){
                while(!st.empty()&&height[st.top()]>height[j]){
                    int length = height[st.top()];
                    st.pop();
                    int weight = st.empty()?j:j-st.top()-1;
                    maxarea=max(maxarea,length*weight);
                }
                st.push(j);            
                }
            while(!st.empty()){
                int length = height[st.top()];
                st.pop();
                int weight = st.empty()?cols:cols-st.top()-1;
                maxarea=max(maxarea,length*weight);
            }
        }
        return maxarea;
    }
};

单调栈模板

	//单减栈(最小栈)
	for(int i =0;i<len;i++){
		while(!st.empty()&&heights[st.top()]>heights[i]){//单增栈(最大栈)只需要改成小于就好
            int height = heights[st.top()];
            st.pop();
            int width = st.empty()?i:i-st.top()-1;
            area = height*width
        }
        st.push(i);
    }
	//如果需要处理栈中剩余元素,则还需要
	while(!st.empty()){
        int height = heights[st.top()];
        st.pop;
        int width = st.empty()?len:len-st.top()-1;
        area = height*width;
    }
  • 58
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值