LeetCode每日一题--85. 最大矩形(单调栈)

题目:跳转至 85. 最大矩形

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

示例 1:

在这里插入图片描述
输入:matrix = [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]
输出:6
解释:最大矩形如上图所示。

示例 2:
输入:matrix = []
输出:0

示例 3:
输入:matrix = [[“0”]]
输出:0

示例 4:

输入:matrix = [[“1”]]
输出:1

示例 5:

输入:matrix = [[“0”,“0”]]
输出:0

提示:

  • rows == matrix.length
  • cols == matrix[0].length
  • 0 <= row, cols <= 200
  • matrix[i][j] 为 ‘0’ 或 ‘1’
class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        
    }
};

思路:
摊牌了不会做,学习题解。

方法一: 使用柱状图的优化暴力方法

首先计算出矩阵的每个元素的左边连续 1 的数量,使用二维数组 left 记录,其中 left[i][j] 为矩阵第 i 行第 j 列元素的左边连续 1 的数量。

随后,对于矩阵中任意一个点,枚举以该点为右下角的全 1 矩形,即将输入转化成了一系列的柱状图,针对每个柱状图计算最大面积。

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        int m=matrix.size();
        if(m==0)
            return 0;
        int n=matrix[0].size();
        vector<vector<int>> left(m,(vector<int>(n,0)));
        for(int i=0;i<m;++i){  //每行从左到右连续为1的长度数,
            for(int j=0;j<n;++j){
                if(matrix[i][j]=='1')
                    left[i][j]=(j==0?0:left[i][j-1])+1;
            }
        }
        int ret=0;  //最大面积
        for(int i=0;i<m;++i){  //对每行统计每列可能的最大矩形面积
            for(int j=0;j<n;++j){
                if(matrix[i][j]=='0')  //如果出现0,那这一点无矩形面积,跳出循环
                    continue;
                int width=left[i][j];  //初始宽度是该行从左到右的连续为1的长度
                int area=width;  //初始面积为初始宽度
                for(int k=i-1;k>=0;--k){  //k从上一行循环至第一行,统计从该点起左上角往左上可能的矩形最大值
                    width=min(width,left[k][j]); //因为矩形内部全为1,所以宽度为所有中最小的
                    area=max(area,(i-k+1)*width);  //面积求取当前点左上矩形最大值
                }
                ret=max(ret,area);
            }
        }
        return ret;
    }
};

方法二:单调栈

对每一列(题解中按列来这里也用列,行应该也是一样的),使用基于柱状图的方法(每一列的柱状图以 # 替代高度在右侧画出)。最大矩形面积需要确认矩形的高和宽。这里宽即是 left 中的值,高为下面要求的 down 与 up 下标的差值减一。

对每一根柱子向上下扩展,分别找到上下两侧最近的宽度小于当前柱子宽的柱子,这就是以当前柱子宽度能扩展到的最大高度。

惯常先举例给自己整明白:

[
  [“1”,“0”,“1”,“0”,“0”],
  [“1”,“0”,“1”,“1”,“1”],
  [“1”,“1”,“1”,“1”,“1”],
  [“1”,“0”,“0”,“1”,“0”]
]

和方法一里面一样先求取 left 得出矩阵第 i 行第 j 列元素的左边连续 1 的数量,如下:

    1 0 1 0 0
    1 0 1 2 3
    1 2 3 4 5
    1 0 0 1 0

j=0 时,由上至下刚开始枚举 1,因为栈为空,1 上侧是柱子是哨兵(特殊值,当前柱子上面没有比他更小的值,假定两个虚拟哨兵在上下两侧,其宽度无限小),记为 -1,放入 up 中,同时下标 0 入栈;
枚举 1,因为 1>=1,移除栈顶元素下标 0,栈为空,同理在 up 中记录哨兵 -1,下标 1 入栈;
余下同理,可得 up:[-1, -1, -1, -1];
由下至上枚举时,第一个也是枚举 1,栈为空,其下侧的柱子为哨兵,记为 4(最大可取下标值加一),放入 down 中,同时下标 3 入栈;
同理,可得 down:[4, 4, 4, 4]
高度为:[4, 4, 4, 4],对应面积为高度与 left [i][0] 的乘积:[4, 4, 4, 4]。

    1 0 1 0 0            #
    1 0 1 2 3   -----> #
    1 2 3 4 5            #
    1 0 0 1 0            #

j=1 时,由上至下刚开始枚举 0,因为栈为空,0 上侧是柱子是哨兵,记为 -1,放入 up 中,同时下标 0 入栈;
枚举 0,因为 0>=0,移除栈顶元素下标 0,栈为空,同理在 up 中记录哨兵 -1,下标 1 入栈;
枚举 2,因为 0<2,不会移除栈顶元素下标 1,所以 2 上侧的柱子是 0 ,在 up 中记录下标 1,下标 2 入栈;
枚举 0,因为 2>=0,移除栈顶元素下标 2,且 0>=0 移除栈顶元素下标 1,栈为空,在 up 中记录哨兵 -1,下标 3 入栈。
可得 up:[-1, -1, 1, -1];
由下至上枚举时,第一个枚举 0,栈为空,其下侧的柱子为哨兵,记为 4,放入 down 中,同时下标 3 入栈;
枚举 2,0<2,不会移除栈顶元素下标 3,2 下侧是柱子为 0,在down中记录下标 3,下标 2 入栈;
余下同理,可得 down:[4, 4, 3, 4]
高度为:[4, 4, 1, 4],对应面积为高度与 left [i][1] 的乘积:[0, 0, 2, 0]。

    1 0 1 0 0
    1 0 1 2 3   ----->
    1 2 3 4 5            ##
    1 0 0 1 0

j=2 时,由上至下刚开始枚举 1,因为栈为空,1 上侧是柱子是哨兵,记为 -1,放入 up 中,同时下标 0 入栈;
枚举 1,因为 1>=1,移除栈顶元素下标 0,栈为空,同理在 up 中记录哨兵 -1,下标 1 入栈;
枚举 2,因为 1<3,不会移除栈顶元素下标 1,所以 3 上侧的柱子是 1 ,在 up 中记录下标 1,下标 2 入栈;
枚举 0,因为 3>=0,移除栈顶元素下标 2,且 1>=0 移除栈顶元素下标 1,栈为空,在 up 中记录哨兵 -1,下标 3 入栈。
可得 up:[-1, -1, 1, -1];
由下至上枚举时,第一个枚举 0,栈为空,其下侧的柱子为哨兵,记为 4,放入 down 中,同时下标 3 入栈;
枚举 3,0<3,不会移除栈顶元素下标 3,3 下侧是柱子为 0,在down中记录下标 3,下标 2 入栈;
枚举 1,3>=1,移除栈顶元素下标 2,又因为 0<1,所以不会移除栈顶元素下标 3,即 1 下侧最近小于其的柱子是 0,将 0 对应的下标 3 放入 down 中,下标 1 入栈;
余下同理,可得 down:[3, 3, 3, 4]
高度为:[3, 3, 1, 4],对应面积为高度与 left [i][2] 的乘积:[3, 3, 3, 0]。

    1 0 1 0 0                #
    1 0 1 2 3   ----->     #
    1 2 3 4 5            ###
    1 0 0 1 0

j=3 时,由上至下刚开始枚举 0,因为栈为空,0 上侧是柱子是哨兵,记为 -1,放入 up 中,同时下标 0 入栈;
枚举 2,因为 0<2,不会移除栈顶元素下标 0,在 up 中记录下标 0,下标 1 入栈;
枚举 4,因为 2<4,不会移除栈顶元素下标 1,在 up 中记录下标 1,下标 2 入栈;
枚举 1,因为 4>=1,移除栈顶元素下标 2,且 2>=1 移除栈顶元素下标 1,又因为 0<1,即在 1 上侧最近的比他小的柱子宽度为 0,在 up 中记录下标 0,下标 3 入栈。
可得 up:[-1, 0, 1, 0];
由下至上枚举时,第一个枚举 1,栈为空,其下侧的柱子为哨兵,记为 4,放入 down 中,同时下标 3 入栈;
枚举 4,1<4,不会移除栈顶元素下标 3,3 下侧是柱子为 0,在down中记录下标 3,下标 2 入栈;
枚举 2,4>=2,移除栈顶元素下标 2,又因为 1<2,所以不会移除栈顶元素下标 3,即 2 下侧最近小于其的柱子是 1,将 1 对应的下标 3 放入 down 中,下标 1 入栈;
枚举 0,2>=0 且 1>=0,所以栈顶元素下标 1 和 3 均出栈,此时栈为空,在 down 中记录哨兵 4,下标0 入栈;
可得 down:[4, 3, 3, 4]
高度为:[4, 2, 1, 3],对应面积为高度与 left [i][3] 的乘积:[0, 4, 4, 3]。

    1 0 1 0 0
    1 0 1 2 3   ----->      ##
    1 2 3 4 5             ####
    1 0 0 1 0                   #

j=4 时,同理可得 up:[-1, 0, 1, -1];
down:[4, 3, 3, 4]
高度为:[4, 2, 1, 4],对应面积为高度与 left [i][4] 的乘积:[0, 6, 5, 0]。

    1 0 1 0 0
    1 0 1 2 3   ----->       ###
    1 2 3 4 5              #####
    1 0 0 1 0

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        int m=matrix.size();
        if(m==0)
            return 0;
        int n=matrix[0].size();
        vector<vector<int>> left(m,(vector<int>(n,0)));
        for(int i=0;i<m;++i){  //每行从左到右连续为1的长度数,
            for(int j=0;j<n;++j){
                if(matrix[i][j]=='1')
                    left[i][j]=(j==0?0:left[i][j-1])+1;
            }
        }
        int ret=0;  //最大面积
        for(int j=0;j<n;++j){  // 对于每一列,使用基于柱状图的方法
            vector<int> up(m,0),down(m,0);
            stack<int> stk;
            for(int i=0;i<m;++i){
                while(!stk.empty() && left[stk.top()][j]>=left[i][j])  //当栈顶元素对应下标的高度大于等于待入栈元素对应下标的高度
                    stk.pop();  //栈顶元素出栈,
                up[i]=stk.empty()?-1:stk.top();  //如果此时栈为空,即所有值都大于等于left[i][j],记为虚拟哨兵-1,最上层,
                stk.push(i);  //下标入栈
            }
            stk=stack<int>();
            for(int i=m-1;i>=0;--i){
                while(!stk.empty() && left[stk.top()][j]>=left[i][j])
                    stk.pop();
                down[i]=stk.empty()?m:stk.top();
                stk.push(i);
            }
            for(int i=0;i<m;++i){
                int height=down[i]-up[i]-1;
                int area=height*left[i][j];
                ret=max(ret,area);
            }
        }
        return ret;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值