没事刷刷题:LeetCode(84) Largest Rectangle in Histogram & (85) Maximal Rectangle
84原题:
Given n non-negative integers representing the histogram's bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.
Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3]
.
The largest rectangle is shown in the shaded area, which has area = 10
unit.
For example,
Given heights = [2,1,5,6,2,3]
,
return 10
.
解决思路:
刚开始打算从动态规划入手,试着写了一下递归方程,发现不合适。但是动态规划提供了一个思路,就是求以当前位置i为最矮柱形时的最大矩形面积,然后按i遍历求最大面积。
如何求以当前位置i为最矮柱形时的最大矩形面积,如果从最朴实的思路考虑,我们对于每个位置i,都分别向左向右遍历,找到对应矩形的左右边界,很容易求取矩形面积,这样的时间复杂度是O(n2)。
这个时间复杂度显然不符合我们的需要。可以考虑优化手段,在向左向右遍历的时候,对于相邻的i,显然有很多重复计算。我们可以设想,假如位置i的柱形,其左边界为Li,右边界为Ri。如果位于i+1的柱形比它更高,那么i+1的左边界是Li+1=i<Li,右边界Ri+1<=Ri。那么计算i+1的对应矩形,是不影响i的矩形的计算的,我们完全可以计算完i+1的矩阵后,在计算Ri时,把Li+1到Ri+1的遍历过程都省略掉。这种思想好像和栈的思路比较吻合。
设计方案:
设置栈s,遍历柱形,当遍历到的柱形高度大于栈顶高度时,入栈,否则,说明栈顶元素(curr)到达了其右边界(当前遍历到的位置i),弹出栈顶元素,计算其矩形面积,放在结果数组的对应位置。它的左边界等于出栈后栈顶元素pre的index(首先pre一定是矮于curr的,否则在遍历到curr时,由于curr矮于pre,pre会被弹出;其次pre至curr之间的元素一定是高于curr的,否则它们应该保留在栈中,pre不应该位于栈顶)。最终计算结果数组的最大值即可。时间复杂度O(n)。
代码:
#include<stack> #include<vector> #include<iostream> using namespace std; int main(){ int hs[12] = {0,1,0,2,1,0,1,3,2,1,2,1}; vector<int> heights; for(int i = 0; i < 12; i++){ heights.push_back(hs[i]); } stack<pair<int, int> > stack; vector<int> results(6); if(heights.size() == 0){ return 0; } else { stack.push(make_pair(0, heights[0])); } for(int i = 1; i < heights.size(); i++){ //遇到大的元素,弹出 while(stack.size() != 0 && stack.top().second > heights[i]){ //计算 pair<int, int> curr = stack.top(); stack.pop(); int index = curr.first; int height = curr.second; int pre; //左边比curr小的元素坐标 if(stack.size() != 0){ pre = stack.top().first; } else { pre = -1; } int post = i; results[index] = (post - pre - 1) * height; } stack.push(make_pair(i, heights[i])); } while(!stack.empty()){ pair<int, int> curr = stack.top(); stack.pop(); int index = curr.first; int height = curr.second; int pre; //左边比curr小的元素坐标 if(stack.size() != 0){ pre = stack.top().first; } else { pre = -1; } int post = heights.size(); results[index] = (post - pre - 1) * height; } int max = 0; for(int i = 0; i < results.size(); i++){ if(results[i] > max){ max = results[i]; } } cout << max << endl; return max; }
85原题:
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题就会有巧妙的思路,将N*M的矩阵压成N个大小为M的数组,将数组套到84题的函数中即可。
具体一点,对于第i行,将0-i行的元素“压扁”作为第i个数组,其第j个元素的值等于从i行向前数,连续的1的个数。如题例中,第三行对应的数组为[ 4, 0, 0, 3, 0],然后将数组丢给84行的函数,即求出以第i行为底的矩形的最大面积。最终对N行求最大即可。时间复杂度O(n2)。
代码:
#include<stack> #include<vector> #include<iostream> using namespace std; int getLineResult(vector<int> heights){ stack<pair<int, int> > stack; vector<int> results(heights.size()); if(heights.size() == 0){ return 0; } else { stack.push(make_pair(0, heights[0])); } for(int i = 1; i < heights.size(); i++){ //遇到大的元素,弹出 while(stack.size() != 0 && stack.top().second > heights[i]){ //计算 pair<int, int> curr = stack.top(); stack.pop(); int index = curr.first; int height = curr.second; int pre; //左边比curr小的元素坐标 if(stack.size() != 0){ pre = stack.top().first; } else { pre = -1; } int post = i; results[index] = (post - pre - 1) * height; } stack.push(make_pair(i, heights[i])); } while(!stack.empty()){ pair<int, int> curr = stack.top(); stack.pop(); int index = curr.first; int height = curr.second; int pre; //左边比curr小的元素坐标 if(stack.size() != 0){ pre = stack.top().first; } else { pre = -1; } int post = heights.size(); results[index] = (post - pre - 1) * height; } int max = 0; for(int i = 0; i < results.size(); i++){ if(results[i] > max) max = results[i]; } return max; } int getMatrixResult(vector<vector<char> > heights){ vector<int> results; vector<vector<int> > new_heights; int N = heights.size(); if(N == 0) return 0; int M = heights[0].size(); if(M == 0) return 0; for(int i = 0; i < N; i++){ vector<int> a, b; new_heights.push_back(b); for(int j = 0; j < M; j++){ new_heights[i].push_back(0); } } for(int i = 0; i < M; i++){ int tmp = 0; for(int j = 0; j < N; j++){ if(heights[j][i] == '0'){ new_heights[j][i] = 0; } else { if(j == 0){ new_heights[j][i] = 1; } else { new_heights[j][i] += new_heights[j-1][i] + 1; } } } } for(int i = 0; i < N; i++){ vector<int> line = new_heights[i]; int r = getLineResult(line); results.push_back(r); } int max = 0; for(int i = 0; i < N; i++){ if(results[i] > max){ max = results[i]; } } return max; } int main(){ int a[4][5] = {{'1','0','1','0','0'},{'1','0','1','1','1'},{'1','1','1','1','1'},{'1','0','0','1','0'}}; vector<vector<char> > d; for(int i = 0; i < 4; i++){ vector<char> tmp; for(int j = 0; j < 5; j++){ tmp.push_back(a[i][j]); } d.push_back(tmp); } cout << getMatrixResult(d) << endl; }
后来看了大神们的源码,思想是类似的,数据结构更为简洁,可以看到分为height、left、right三个数组,表征的是,以当前的位置i,j所在的行作为矩形的底,以matrix[i][j]向上的连续的1的个数作为高的矩形,其高、左边界、右边界的值。(这里的左右边界与我们的解法中的左右边界分别大/小1,即表示的是“1”的取值范围)
行之间的数组存在一定的继承关系,height上的继承自不必说,左右边界具体表现在left[j] = max(left[j], currleft)与right[j] = min(right[j], currright)这两句。以左边界为例,当上一层为“0”时,left[j]继承来的值为0,currleft为从左边遍历来的左边界,max后取值正确;当上一层不为“0”时,对于对于上一层高为height[j]-1的左边界,如果在这一行相应的位置均为1的话,自然最好,left[j]保持不变;不行的话,取从左边取到的左边界currleft,left[j]=max(left[j], currleft)=currleft,是在将左边界尽量向右以满足矩形限制。
int maximalRectangle(vector<vector<char>>& matrix) { if(!matrix.size() || !matrix[0].size()) return 0; int m = matrix.size(); int n = matrix[0].size(); vector<int> left(n, 0); vector<int> right(n, n); vector<int> height(n, 0); int maxArea = 0; for(int i = 0; i < m; i++) { int currleft = 0, currright = 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], currleft); else { left[j] = 0; currleft = j + 1; } } for(int j = n - 1; j >= 0; j--) { if(matrix[i][j] == '1') right[j] = min(right[j], currright); else { right[j] = n; currright = j; } } for(int j = 0; j < n; j++) maxArea = max(maxArea, (right[j] - left[j]) * height[j]); } return maxArea; }