如果说给你一个直方图,每个直方条的宽度为一,问你其中的某些直方条连在一起组成的最大的矩形面积是多少?
其实这道题就是相当于给你一个int height数组,表示每个直方条的高度,让你求最大的矩形的面积。我们知道对于每个直方条i,我们按照贪心的思路,将左右两边所有的height[j]>=height[i]的直方条都连在一起的话,得到的肯定是包含直方条i的最大矩形面积。
于是问题现在变成了对于当前的直方条i,我们需要求l[i]和r[i]分别表示左右两边所能延伸到的最远的直方条编号,如果我们已经知道了l[]和r[],那么我们显然是可以在O(n)的时间内求的最大的矩形的面积的,
也就是max(height[i] * (r[i]-l[i]+1))。
那么我们如何在O(n)的时间内得到l[]和r[]呢?
我们只说明如何求的l[]即可,其中l[i] = k表示最左边的直方条k,height[k]>=height[i]。
如果height[] = {2, 1, 5, 6, 2, 3},
那么此时的l[] = {0, 0, 2, 3, 2, 5}。
我们发现如果height[j] >= height[i],那么在区间[height[j], j]内的所有元素都是大于等于height[i],于是使用这个思路我们跳跃求i对应的l[i]。
那么为什么这样做的时间复杂度就是O(n)呢?我们来看对于任意一个j,如果height[j]>=height[i],那么区间[l[j], j]将会被[l[i], i]覆盖,于是这样被覆盖后的j是永远不会被重复访问第二次的,为什么呢?因为我们从右到左的比较height[i]与height[i`]的关系是,如果height[i] >= height[i`],于是区间[l[j], j]的height值一定也大于等于height[i`],也就是说直接将[l[i`], i`]区间覆盖了区间[l[i], i],也就覆盖了区间[l[j], j],所以j不会被重复访问第二次。因此每个i只会被访问一次,加上遍历的一边,也就是复杂度近似O(2n)的。
于是总的复杂度就是O(n)。
class Solution {
public:
int largestRectangleArea(vector<int> &height) {
int n = height.size();
int *r = new int[n];
int *l = new int[n];
for(int i=0; i<n; i++){
l[i] = i;
int j = i-1;
while(j>=0 && height[j]>=height[i]){
l[i] = l[j];
j = l[j] - 1;
}
}
for(int i=n-1; i>=0; i--){
r[i] = i;
int j = i+1;
while(j<n && height[j]>=height[i]){
r[i] = r[j];
j = r[j] + 1;
}
}
int ans = 0;
for(int i=0; i<n; i++){
int temp = height[i]*(r[i]-l[i]+1);
if(temp > ans)
ans = temp;
}
delete[] l;
delete[] r;
return ans;
}
};
在下面这个非常经典的问题中也可以使用该方法进行优化。
求一个01矩阵中最大的1矩阵,如果我们直接暴力求解,时间复杂度是O(n^4)的;而如果稍加优化,首先预处理成每一行为一个直方图问题,在每一行的直方图问题上进行暴力枚举每个连续的区间,那么总的时间复杂度是O(n^3)的;而如果我们对每一行的直方图问题使用上面的思路来求解,于是总的复杂度就降到了O(n^2),这显然是一个非常好的优化。