给定直方图,求直方图中最大的矩形面积。
例如下图,可以用数组表示为[2,1,5,6,2,3]。
对应的最大矩形面积为10.
对每个左边界,可以枚举其右边界的位置,寻找面积最大值。
int h[] = {2,1,5,6,2,3};
int length = 6, max = 0, s = 0;
for(int i = 0; i < length; i ++) {
for(int j = i + 1; j < length; j ++) {
if (h[i] > h[j]) break; //右边低于左边,找到右边界
}
s = h[i] * (j - 1);
if (max < s) max = s;
}
枚举的时间为复杂度为O(n2),空间复杂度为O(1)。
用动态规划来考虑这个问题。动态规划必须找到一个可以在相邻柱状条之间转换的状态描述。相邻柱状条除了比大小,再没有别的关系可用了。
所以,定义left[i]表示往左边扩展,高度不低于h[i]的连续柱状条个数。
right[i]表示往右边扩展,高度不低于h[i]的连续柱状条个数。
这样,以h[i]为高度的最大矩形面积为h[i]*(left[i]+1+right[i])。
枚举所有位置,就可以找到最大矩形了,而且一定可以找到。因为直方图中的最大矩形,一定有一条最低的柱状条。
参考代码如下:
int h[] = {2,1,5,6,2,3};
int length = 6, max = 0, s = 0;
int left[6] = {0}, right[6] = {0};
for(int i = 1; i < length;i ++)
left[i] = (h[i] <= h[i-1] ? left[i-1] + 1 : 0);
for(int i = length - 2; i >= 0; i --)
right[i] = (h[i] <= h[i+1] ? right[i+1] + 1: 0);
for(int i = 0; i < length; i ++) {
s = h[i] * (left[i] + 1 + right[i]);
if (max < s) max = s;
}
动态规划时间复杂度为O(n),空间复杂度也为O(n)。
网上广为流传的是一种利用单调栈思路的代码。基本原理描述如下:
扫描数组,并维护一个单调栈,栈里面的元素是递增的。如果当前元素比栈顶元素大,入栈。否则,出栈,直到当前元素大于栈顶元素。每次出栈时,计算以当前栈顶元素为高度的最大矩形。
代码如下:
vector<int> h = {2,1,5,6,2,3};
int length = 6, max = 0;
stack<int> s;
h.push_back(0); //哨兵,用于清空单调栈
for(int i = 0; i < length; i ++) {
while(!s.empty() && h[i] <= h[s.top()]) {
int cur = s.top();
s.pop();
int r = h[cur] * (s.empty() ? i : i - s.top() - 1);
max = (max < r : r : max);
}
s.push(i);
}
该方法的时间复杂度是O(n),空间复杂度也为O(n)。
单调栈的作用,在于保存了每个柱状条往前比自己高的元素信息。每个出栈元素,必定是大于当前元素的。因而,其构成的最大矩形,也只能到当前元素为止。