题目:跳转至 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;
}
};