Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones and return its area.
一道hard题,但没有规定复杂度要求,所以我先用最基本的方法做了遍。对比在一个一位数组求直方图最大面积问题,每个数对应的可以有的最大面积是以它为最小值的最长序列,所以,找到一个标准开始循环就行。
方法一:暴力循环,O(n^4)
我选的标准就是逐个扫描点,考虑它作为矩形左上角对应的最大矩形面积。当发现1时,外循环停下,开始从这个店向右向下找。假设整个matrix是n*n,这种做法复杂度就是O(n^4), 虽然low了点,但是leetcode还是可以接受的,然而我自己做也就会这么写了。如下:
int maximalRectangle(vector<vector<char>>& matrix) {
int H = matrix.size();
if (H == 0) return 0;
int W = matrix[0].size();
int i, j, tempi, tempj, tempW;//i,j代表大循环坐标,tempi,tempj代表找矩形的小循环坐标
int area, maxrect=0;
for (i = 0; i < H; i++) {
for (j = 0; j < W; j++) {
// 找到1之后以它为左上角搜索最大面积
if (matrix[i][j] == '1') {
tempW = W;
for (tempi = i; tempi < H; tempi++) {
if (matrix[tempi][j] == '1') {
// 换行后如果首字不是1,直接break
for (tempj = j; tempj < tempW; ) {
if (matrix[tempi][tempj] == '0')
break;
else
tempj++;
}
// 更新宽度右边界
tempW = min(tempW, tempj);
// 搜索完每行更新一下最大面积
area = (tempi - i + 1) * (tempj - j);
maxrect = max(maxrect, area);
}
else
break;
}
}
}
}
return maxrect;
}
方法二:动态规划优化,复杂度O(n^3)
同样的思路,但不用每次都向右向下循环。可以先建一个表,存下从这点开始,这一行还能连续有几个1。
然后大循环遇到1停下来只用向下找就可以了。
int maximalRectangle(vector<vector<char>>& matrix) {
int H = matrix.size();
if (H == 0) return 0;
int W = matrix[0].size();
vector<vector<int>> dp(H, vector<int>(W));
int i, j, tempi, tempW;
int maxrect = 0, area;
// 初始化dp矩阵
for (i = 0; i < H; i++)
dp[i][W - 1] = (matrix[i][W - 1] == '1');
for (i = 0; i < H; i++)
for (j = W - 2; j >= 0; j--) {
if (matrix[i][j] == '1')
dp[i][j] = 1 + dp[i][j + 1];
else
dp[i][j] = 0;
}
// 枚举所有点,同样作为左上角
for (i = 0; i < H; i++) {
for (j = 0; j < W; j++) {
// 预判,如果此时最大可能面积都不够,直接结束这一行
if ((H - i)*(W - j) < maxrect) break;
tempW = dp[i][j];
for (tempi = i; tempi < H && tempW > 0; tempi++) {
// 随着最小宽度的变化,可是适时终止判断
if (tempW > dp[tempi][j]) tempW = dp[tempi][j];
if (tempW * (H - i) < maxrect) break;
area = tempW * (tempi - i + 1);
maxrect = max(area, maxrect);
}
}
}
return maxrect;
}
这里有两次预判可以注意下,适当的减少了很多不必要的判断。如果当前最大的可能性都不比现有的最大面积大的话,不需要徒劳循环,方法一时间上超过10%,方法二超过了70%,其实基本思路已经get了,但差别就是这么大。
方法三:将二维问题回归一维,O(n^2)
这个厉害了,虽然我也想到了联系之前的直方图找最大面积, largest Rectangle in histogram, 这里可以每一行看做一个直方图,用方法二建表的思想可以先建立一个二维的height矩阵,每一行代表从这一行向上可以有的连续1的个数,然后这一行就是一个直方图问题,然后就可以用stack实现O(n)复杂度的面积,然后循环每一行就可以。简直棒呆!
class Solution {
public:
// 一维数组直方图最大面积的线性方法应该熟悉
int largestRectangleArea(vector<int>& height) {
if (height.size() == 0) return 0;
height.push_back(0); // 结尾加0
int i, maxrect = 0, temp;
stack<int> s;
for (i = 0; i < height.size(); ) {
if (s.empty() || height[i] >= height[s.top()]) {
s.push(i);
i++;
}
//保持递增序列,遇到右边界弹出计算
else {
temp = s.top();
s.pop();
if (s.empty())
maxrect = max(maxrect, height[temp] * i);//没有左边界就直接是现有长度乘它
else
maxrect = max(maxrect, height[temp] * (i - s.top() - 1));//有左边界就算长度
}
}
return maxrect;
}
int maximalRectangle(vector<vector<char>>& matrix) {
int H = matrix.size();
if (H == 0) return 0;
int W = matrix[0].size();
vector<vector<int> > height(H, vector<int>(W, 0));
int i, j, maxrect = 0;
// 将每一行都构造成一个直方图
for (i = 0; i < H; i++) {
for (j = 0; j < W; j++) {
if (matrix[i][j] == '0')
height[i][j] = 0;
else
height[i][j] = (i == 0) ? 1 : (height[i - 1][j] + 1);
}
}
// 用之前算法解决每一行的最大面积
for (i = 0; i < H; i++) {
maxrect = max(maxrect, largestRectangleArea(height[i]));
}
return maxrect;
}
};
总之,这一题在我自己做了O(n^4)的解法后,学习了更优化的两种写法,确实感慨不已。