Given a 2D binary matrix filled with 0’s and 1’s, find the largest rectangle containing only 1’s and return its area.
For example, given the following matrix:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
Return 6.
第一种方法:可以借用84. Largest Rectangle in Histogram的思路。
假设把矩阵沿着某一行分开,然后把分开的行作为底面,将自底面往上的矩阵看成一个直方图(histogram)。直方图的中每个项的高度就是从底面行开始往上1的数量。根据Largest Rectangle in Histogram就可以求出当前行作为矩阵下边缘的一个最大矩阵。接下来如果对每一行都做一次Largest Rectangle in Histogram,从其中选出最大的矩阵,那么它就是整个矩阵中面积最大的子矩阵。
如果知道上一行直方图的高度,就只需要看新加进来的行(底面)上对应的列元素是不是0,如果是,则高度是0,否则则是上一行直方图的高度加1。
这样时间复杂度为O(m*n)
class Solution {
public:
int maximalRectangle(vector<vector<char> >& matrix)
{
if(matrix.size()==0 || matrix[0].size()==0)
return 0;
int m=matrix.size();
int n=matrix[0].size();
int i,j,area=0;
vector<int>heights(n,0);
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
{
if(matrix[i][j]=='1')
heights[j]+=1;
else
heights[j]=0;
}
area=area>largestRectangleArea(heights)?area:largestRectangleArea(heights);
}
return area;
}
int largestRectangleArea(vector<int>& heights)
{
int area=0,i;
stack<int>temp;
for(i=0;i<heights.size();i++)
{
if(temp.empty())
temp.push(heights[i]);
else
{
if(heights[i]>=temp.top())
temp.push(heights[i]);
else
{
int count=1;
while(!temp.empty()&&heights[i]<temp.top())
{
area=temp.top()*count>area?temp.top()*count:area;
temp.pop();
count++;
}
while(count)
{
temp.push(heights[i]);
count--;
}
}
}
}
int num=1;
while(!temp.empty())
{
if(temp.top()*num>area)
{
area=temp.top()*num;
}
num++;
temp.pop();
}
return area;
}
};
第二种方法:暴力求解
一般一个题目我首先会想想怎么暴力解决,比如这一题,可以枚举出所有的矩形,求出其中的面积最大者,那么怎么枚举呢,如果分别枚举矩形的宽度和高度,这样还得枚举矩形的位置,复杂度至少为O(n^4) (计算复杂度是我们把matrix的行、列长度都泛化为n,下同),我们可以枚举矩形左上角的位置,那么知道了矩形左上角的位置,怎么计算以某一点为左上角的矩形的最大面积呢?举例如下,下面的矩阵我们以(0,0)为矩形的左上角:
1 1 1 1 0 0
1 1 1 0 1 1
1 0 1 0 1 1
0 1 1 1 1 1
1 1 1 1 1 1
矩形高度是1时,宽度为第一行中从第一个位置起连续的1的个数,为4,面积为4 * 1 = 4
矩形高度是2时,第二行从第一个位置起连续1的个数是3,宽度为min(3,4) = 3,面积为3*2 = 6
矩形高度为3时,第三行从第一个位置起连续1的个数是1,宽度为min(1,3) = 1,面积为1*3 = 3
矩形高度为4时,第四行从第一个位置起连续1的个数是0,宽度为min(0,1) = 0,面积为0*4 = 0
后面的行就不用计算了,因为上一行计算的宽度是0,下面所有宽度都是0
因此以(0,0)为左上角的矩形的最大面积是6,计算以某一点为左上角的矩形的最大面积复杂度是O(n)。
注意到上面我们用到了信息“从某一行某个位置开始连续的1的个数”,这个我们可以通过动态规划求得:设dp[i][j]是从点(i,j)开始,这一行连续1的个数,动态规划方程如下:
初始条件:dp[i][m-1] = (matrix[i][m-1] == ‘1’) (m是matrix的列数)
dp[i][j] = (matrix[i][j] == ‘1’) ? 1 + dp[i][j + 1] : 0 ,(从方程看出我们应该从每一行的后往前计算)
计算dp复杂度O(n^2),枚举左上角位置以及计算以该位置为左上角的矩形最大面积复杂度是O(n^2*n)=O(n^3),总的复杂度是O(n^3).
这个算法还可以优化,枚举到某个点时我们可以假设该点右下方全是1,得到一个假设最大面积,如果这个面积比当前计算好的面积还要小,该点就可以直接跳过;
class Solution {
public:
int maximalRectangle(vector<vector<char> >& matrix)
{
if(matrix.size()==0||matrix[0].size()==0)
return 0;
int m=matrix.size();
int n=matrix[0].size();
vector<vector<int> >dp(m,vector<int>(n,0));
int i,j,k;
for(i=0;i<m;i++)
dp[i][n-1]=matrix[i][n-1]-'0';
for(i=0;i<m;i++)//求出所有的dp值
for(j=n-2;j>=0;j--)
{
if(matrix[i][j]=='0')
dp[i][j]=0;
else
dp[i][j]=1+dp[i][j+1];
}
int area=0;
//以每个点作为矩形的左上角计算所得的最大矩形面积
for(i=0;i<m;i++)
for(j=0;j<n;j++)
{
//剪枝,n-j是最大宽度,m-i是最大高度
if((m-i)*(n-j)<area)
break;
int width=INT_MAX;
for(k=i;k<m;k++)
{
//剪枝,m-i是以点(i,j)为左上角的矩形的最大高度
if(width*(m-i)<=area)
break;
width=dp[k][j]<width?dp[k][j]:width;//矩形宽度要取从第i行到第k行宽度的最小值
area=(k-i+1)*width>area?(k-i+1)*width:area;
}
}
return area;
}
};
第三种方法:动态规划
思路同样是从第一行开始一行一行地处理,使[i, j]处最大子矩阵的面积是(right(i, j)-left(i, j))*height(i, j)。其中height统计当前位置及往上’1’的数量;left和right是高度是当前点的height值得左右边界,即是以当前点为中心,以height为高度向两边扩散的左右边界。
递推公式如下:
left(i, j) = max(left(i-1, j), cur_left);
right(i, j) = min(right(i-1, j), cur_right);
height(i, j) = height(i-1, j) + 1, if matrix[i][j]==’1’;
height(i, j) = 0, if matrix[i][j]==’0’.
其中cur_left和cur_right的值由当前行决定,如果当前位置是’1’,则cur_left和cur_right都不变;如果当前位置是’0’,则cur_left就为当前位置的右侧,cur_right就为当前位置(因为左闭右开)。
时间复杂度:O(mn)
空间复杂度:O(n)
class Solution {
public:
int maximalRectangle(vector<vector<char> >& matrix)
{
if(matrix.size()==0||matrix[0].size()==0)
return 0;
int m=matrix.size();
int n=matrix[0].size();
int i,j,area=0;
vector<int>heights(n,0);
vector<int>left(n,0);
vector<int>right(n,n);
for(i=0;i<m;i++)
{
int cur_left=0,cur_right=n;
for(j=0;j<n;j++)
{
if(matrix[i][j]=='1')
heights[j]+=1;
else
heights[j]=0;
}
for(j=0;j<n;j++)
{
if(matrix[i][j]=='1')
left[j]=left[j]>cur_left?left[j]:cur_left;
else
{
left[j]=0;
cur_left=j+1;
}
}
for(j=n-1;j>=0;j--)
{
if(matrix[i][j]=='1')
right[j]=right[j]<cur_right?right[j]:cur_right;
else
{
right[j]=n;
cur_right=j;
}
}
for(j=0;j<n;j++)
area=area>(right[j]-left[j])*heights[j]?area:(right[j]-left[j])*heights[j];
}
return area;
}
};