给定一个只含0和1的数组,求含1的最大矩形面积。
Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones and return its area.
这样的题一般看来都是有O(n*m)的解法的。
借助上一题 Largest Rectangle in Histogram 的解法。
我们现在把矩阵的每一行当做是上一题的问题,然后遍历所有的行数,取最大数就是解。
这里的每一行的高度怎么确定呢。
1.如果当前点为‘0’,那么高度就是0
2.如果当前点为‘1’,那么高度就是往上连续1的个数(包括自己)。
求高度是一个动态规划过程。用dp[i][j]来存某个位置的高度那么
对于第i行第j列的点:
if (matrix[i][j] == '1')
dp[i][j] = dp[i-1][j] + 1;
else
dp[i][j] = 0;
class Solution { public: // 84 int largestRectangleArea(vector<int> &height) { height.push_back(0); int maxarea = 0; int i = 0; stack<int> stk; while(i < height.size()) { if (stk.empty() || height[stk.top()] < height[i]) { stk.push(i++); } else { int t = stk.top(); stk.pop(); maxarea = max(maxarea, height[t]*(stk.empty()?i:i-stk.top()-1)); } } return maxarea; } //85 int maximalRectangle(vector<vector<char> > &matrix) { if (matrix.size() == 0 || matrix[0].size()==0) return 0; int row = matrix.size(), col = matrix[0].size(), ans = 0; vector<vector<int> > dp(row, vector<int>(col)); for (int j = 0; j < col; j++) dp[0][j] = matrix[0][j] - '0'; for (int i = 1; i < row; i++) //动态求每行的histogram高度 for (int j = 0; j < col; j++) { if (matrix[i][j] == '1') dp[i][j] = dp[i-1][j] + 1; else dp[i][j] = 0; } for (int i = 0; i < row; i++) { ans = max(ans, largestRectangleArea(dp[i])); } return ans; } };
如上是O(n*m)的解法。其实我觉得这个大神的O(n^3)的解法解释也挺好理解。
分析:一般一个题目我首先会想想怎么暴力解决,比如这一题,可以枚举出所有的矩形,求出其中的面积最大者,那么怎么枚举呢,如果分别枚举矩形的宽度和高度,这样还得枚举矩形的位置,复杂度至少为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复杂度O(n^2),枚举左上角位置以及计算以该位置为左上角的矩形最大面积复杂度是O(n^2*n)=O(n^3),总的复杂度是O(n^3)
这个算法还可以优化,枚举到某个点时我们可以假设该点右下方全是1,得到一个假设最大面积,如果这个面积比当前计算好的面积还要小,该点就可以直接跳过;在上面计算以某点为左上角的矩形面积时,也可以剪枝,剪枝方法同上。具体可以参考代码注释。
class Solution { public: int maximalRectangle(vector<vector<char> > &matrix) { int row = matrix.size(); if(row == 0)return 0; int column = matrix[0].size(); int dp[row][column], res = 0; memset(dp, 0, sizeof(dp)); //求出所有的dp值 for(int i = 0; i < row; i++) dp[i][column-1] = (matrix[i][column-1] == '1'); for(int i = 0; i < row; i++) for(int j = column - 2; j >= 0; j--) if(matrix[i][j] == '1') dp[i][j] = 1 + dp[i][j + 1]; //以每个点作为矩形的左上角计算所得的最大矩形面积 for(int i = 0; i < row; i++) { for(int j = 0; j < column; j++) { //剪枝,column-j是最大宽度,row-i是最大高度 if((column - j) * (row - i) <= res)break; int width = dp[i][j]; for(int k = i; k < row && width > 0; k++) { //剪枝,row-i是以点(i,j)为左上角的矩形的最大高度 if(width * (row - i) <= res)break; //计算以(i.j)为左上角,上下边缘是第i行和第k行的矩形面积 if(width > dp[k][j])width = dp[k][j];//矩形宽度要取从第i行到第k行宽度的最小值 res = max(res, width * (k - i + 1)); } } } return res; } };