题目:
Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones and return its area.
题意:
给定一个充满0和1的二维矩阵,找到包含最多1的矩形并返回其面积。
思路一:转载地址:http://blog.csdn.net/linhuanmars/article/details/24444491
原文:原博文地址。
这是一道非常综合的题目,要求在0-1矩阵中找出面积最大的全1矩阵。刚看到这道题会比较无从下手,brute force就是对于每个矩阵都看一下,总共有m(m+1)/2*n(n+1)/2个子矩阵(原理跟字符串子串类似,字符串的子串数有n(n+1)/2,只是这里是二维情形,所以是两个相乘),复杂度相当高,肯定不是面试官想要的答案,就不继续想下去了。
这道题的解法灵感来自于 Largest Rectangle in Histogram 这道题,假设我们把矩阵沿着某一行切下来,然后把切的行作为底面,将自底面往上的矩阵看成一个直方图(histogram)。直方图的中每个项的高度就是从底面行开始往上1的数量。根据Largest Rectangle in Histogram 我们就可以求出当前行作为矩阵下边缘的一个最大矩阵。接下来如果对每一行都做一次 Largest Rectangle in Histogram ,从其中选出最大的矩阵,那么它就是整个矩阵中面积最大的子矩阵。
算法的基本思路已经出来了,剩下的就是一些节省时间空间的问题了。
我们如何计算某一行为底面时直方图的高度呢? 如果重新计算,那么每次需要的计算数量就是当前行数乘以列数。然而在这里我们会发现一些动态规划的踪迹,如果我们知道上一行直方图的高度,我们只需要看新加进来的行(底面)上对应的列元素是不是0,如果是,则高度是0,否则则是上一行直方图的高度加1。利用历史信息,我们就可以在线行时间内完成对高度的更新。我们知道, Largest Rectangle in Histogram的算法复杂度是O(n)。所以完成对一行为底边的矩阵求解复杂度是O(n+n)=O(n)。接下来对每一行都做一次,那么算法总时间复杂度是O(m*n)。
空间上,我们只需要保存上一行直方图的高度O(n),加上 Largest Rectangle in Histogram 中所使用的空间O(n),所以总空间复杂度还是O(n)。代码如下:16ms
public int maximalRectangle(char[][] matrix) { if(matrix==null || matrix.length==0 || matrix[0].length==0) { return 0; } int maxArea = 0; int[] height = new int[matrix[0].length]; for(int i=0;i<matrix.length;i++) { for(int j=0;j<matrix[0].length;j++) { height[j] = matrix[i][j]=='0'?0:height[j]+1; } maxArea = Math.max(largestRectangleArea(height),maxArea); } return maxArea; } public int largestRectangleArea(int[] height) { if(height==null || height.length==0) { return 0; } int maxArea = 0; LinkedList<Integer> stack = new LinkedList<Integer>(); for(int i=0;i<height.length;i++) { while(!stack.isEmpty() && height[i]<=height[stack.peek()]) { int index = stack.pop(); int curArea = stack.isEmpty()?i*height[index]:(i-stack.peek()-1)*height[index]; maxArea = Math.max(maxArea,curArea); } stack.push(i); } while(!stack.isEmpty()) { int index = stack.pop(); int curArea = stack.isEmpty()?height.length*height[index]:(height.length-stack.peek()-1)*height[index]; maxArea = Math.max(maxArea,curArea); } return maxArea; }这道题最后的复杂度是非常令人满意的,居然在O(m*n)时间内就可以完成对最大矩阵的搜索,可以看出这已经是下界(因为每个元素总要访问一下才知道是不是1)了。难度还是比较大的,相信要在面试当场想到这种方法是很不容易的。
思路二:转载地址:https://leetcode.com/discuss/20240/share-my-dp-solution
DP动态规划解决方法:对矩阵每一行进行扫描,找到每行元素的左边界与右边界以及高,取面积最大的。
动态规划的状态转移方程式:
left(i,j) = max(left(i-1,j), curleft), curleft can be determined from the current row
right(i,j) = min(right(i-1,j), curright), curright can be determined from the current row
height(i,j) = height(i-1,j) + 1, if matrix[i][j]=='1';
height(i,j) = 0, if matrix[i][j]=='0'
代码:
class Solution { public: int maximalRectangle(vector<vector<char>>& matrix) { if(matrix.empty()){ return 0; } const int m = matrix.size(); const int n = matrix[0].size(); int left[n], right[n], height[n]; fill_n(left,n,0); fill_n(right,n,n); fill_n(height,n,0); int maxArea = 0; for(int i=0; i<m; i++){ int cur_left = 0; int cur_right = n; for(int j=0; j<n; j++){ //对应元素的高 if(matrix[i][j] == '1'){ height[j]++; }else{ height[j] = 0; } } for(int j=0; j<n; j++){ //左边界 if(matrix[i][j] == '1'){ left[j] = max(left[j], cur_left); }else{ left[j] = 0; cur_left = j+1; } } for(int j=n-1; j>=0; j--){ //右边界 if(matrix[i][j] == '1'){ right[j] = min(right[j], cur_right); }else{ right[j] = n; cur_right = j; } } for(int j=0; j<n; j++){ maxArea = max(maxArea, (right[j]-left[j])*height[j]); } } return maxArea; } };
If you think this algorithm is not easy to understand, you can try this example:
0 0 0 1 0 0 0
0 0 1 1 1 0 0
0 1 1 1 1 1 0
The vector "left" and "right" from row 0 to row 2 are as follows
row 0:
l: 0 0 0 3 0 0 0
r: 7 7 7 4 7 7 7
row 1:
l: 0 0 2 3 2 0 0
r: 7 7 5 4 5 7 7
row 2:
l: 0 1 2 3 2 1 0
r: 7 6 5 4 5 6 7
The vector "left" is computing the left boundary. Take (i,j)=(1,3) for example. On current row 1, the left boundary is at j=2. However, because matrix[1][3] is 1, you need to consider the left boundary on previous row as well, which is 3. So the real left boundary at (1,3) is 3.
I hope this additional explanation makes things clearer.