单调栈是一个很好用的工具,可以高效的将一些
O
(
n
2
)
O(n^2)
O(n2)的问题优化到
O
(
n
)
O(n)
O(n) 经典问题如:POJ:2559 Largest Rectangle in a Histogram。如下直方图,每个直方柱的宽度为1,高度如下所示,请问由连续直方柱构成的最大矩形面积?
O
(
n
2
)
O(n^2)
O(n2)的算法很容易想到,因为最终的矩形一定等于某个直方柱的高度,因此尝试每一个直方柱的高度
h
i
h_i
hi向左右两侧扩展,扩展的截止条件是两侧第一个低于
h
i
h_i
hi的高度为止。
当然我们对 O ( n 2 ) O(n^2) O(n2)的算法是不满意的,因此我们可以构建单调栈。我们发现,如果直方柱的高度是单调递增的话,那么以每一个柱子为高度的矩形面积都可以向右持续增加。如下图所示,这种情况下我们可以将这些柱子依次存入栈中。
![](https://i-blog.csdnimg.cn/blog_migrate/45c2e7eb3f0d415ac7ffb53b24d6cd81.jpeg)
- 取出8号高度为16的柱子,可以算出以8号柱子为高度的矩形最大面积为16×1 = 16
- 取出7号高度为14的柱子,可以算出以7号柱子为高度的矩形最大面积为14×2 = 28
- 取出6号高度为12的柱子,可以算出以6号柱子为高度的矩形最大面积为12×3 = 36
这里需要注意的是,我们怎么知道上述高度为16,14,12几个高度要分别乘1,2,3,实际上我们在栈中存储每个直方柱的下标即可。然后可以简单的根据下标计算要出栈的柱子跟当前遍历到的柱子的距离(以出栈柱子为高度可以向右扩展的最远处)
![](https://i-blog.csdnimg.cn/blog_migrate/166af819f26bd02eaa7dad26762666d3.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/5d579e138ef94b3d94fafe2394800015.jpeg)
1)如果所有柱子的高度是递增,那么如何计算面积? 因为全部递增,我们只会一直push,一直push就不会计算面积。为了使模型简化,我们可以在最末尾加一个高度为0(或-1)的直方柱,使得其对与前面的任何柱子都是逆序,因此可以取出前面所有的柱子并计算面积。
![](https://i-blog.csdnimg.cn/blog_migrate/83e07091c62892c4fab2c201ba589c2f.jpeg)
2)如果所有柱子的高度是递减,那么如何计算面积? 如下所示,首先1号柱子入栈,当遍历2号柱子时,1号柱子出栈,它跟2号柱子的距离为1,因此以16为高度的最大矩形面积为:16×1 = 16, 然后2号柱子进栈,接下来遍历3号柱子,它右跟栈顶的2号柱子构成逆序,因此2号柱子出栈,那么2号柱子虽然无法向右扩展,但可以一直向左扩展到最左侧,那么这里需要特殊处理,如果某个柱子出栈后栈空,说明该柱子之前的柱子都比它高,否则不会就它一个在栈里,回顾单调栈的意义。因此,这种情况只需要将柱子高度乘以柱子下标即可,因此以2号柱子为高度的最大矩形面积为14×2=28。依次类推,以3号柱子为高度的最大矩形面积为13*3=36… 或者更简单的方法,开始先push一个高度为0的柱子,这样最简单。
![](https://i-blog.csdnimg.cn/blog_migrate/9f49717e1341feaca7dac06c84d9ca04.jpeg)
int s[N] = { 0,6, 2, 5, 4, 5, 1, 6, 0 },maxArea; //从1开始
int GetMaxArea(){
stack<int> S; S.push(0);
for (int i = 1; i<N; i++){
if (S.empty()) S.push(i); //栈为空,直接进栈
else if (s[S.top()] <= s[i]) S.push(i); //栈中元素<当前元素,直接进入
else{
while (!S.empty() && s[S.top()] > s[i]){ //不为空且大于当前元素
int index = S.top(), area = 0;
S.pop();
if (!S.empty()) area = s[index] * (i - index);
else area = s[index] * index;
maxArea = max(maxArea,area);
}
S.push(i);
}
}
return maxArea;
}