题目
题目来源https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
解题思路
开始的思路
一开始我是这样想的:先遍历一遍数组,找到最短的那条边,求出以最短边为高的矩形面积(既然是最短边,那么它的底肯定是数组长度),然后再分别求出最短边的左边和右边的最大矩形面积,这又可以归结为同样的问题,那么就可以利用分治法解决,最后在这三者的值中选最大的那个。
所以这道题可以利用分治法解决,但是在实际编程的过程中,遇到几个问题:
第一个是如何将最短边的左边和右边的数组分离出来,也就是说如何表示最短边左边和右边的数组,其次,在某种极端情况下,如该数组是一个单调递增/递减的数组,那么这个时间效率不是很好
利用单调栈解决问题
主要参考下篇博客:https://blog.csdn.net/Zolewit/article/details/88863970
首先我们先来理解一下什么是单调栈
单调栈
定义:单调栈是一种特殊的栈,其栈内元素都保存着单调的特性,单调递增或者单调递减,一般是这样子定义的:
stack<int> s;
具体解决思路
首先,我们做这道题如何利用栈来解题呢?我们首先明确一个问题,那就是在几条边组成的矩形中,这个矩形的高永远是最短的一条。
好了,那么我们开始对问题进行分析,要求能描绘的矩形面积最大,首先会有很多情况,比如有可能是连续的几条最高的边能描绘成最大的矩形,最大矩形也有可能是以最短边为高的矩形,所以这道题应该不能用类似于直接定位到某两个点,这两个点构成的矩形面积最大,所以应该还是得遍历一遍数组,在遍历的过程中不断改变最大矩形的面积。
一个思路就是求出以每条边为高的矩形所能构成的最大面积,这样在其中选出最大值即可,那么这个思路有什么难点呢?最主要要解决的问题就是:我如何能知道后边的边是否可以纳入我这个矩形呢,我们肯定不可能未卜先知,所以还是得遍历到才能知道是不是可以用这条边。那么就转换成了解决这样的矛盾:此时我这条边能和多少条边构成的矩形面积最大?
我们还需要知道的是,一条边能和其他边构成最大矩形面积,那么其他边肯定比此边大或者相等,那么这个就跟我们单调栈的性质联系上了。
所以,我们是这样子解决问题的:
首先为了避免陷入异常,我们需要在数组的最末端设置一个最短边,这样做的目的是防止因为数组是单调递增的情况而求不出结果(因为如果数组是单调递增的话,那么程序就会一直压栈,最后当遍历完所有的数组元素,则直接退出,此时栈内保持全部的数组元素,程序返回的是表示矩形最大面积的初始值)
heights.push_back(0);
接着我们就需要设置单调栈:
stack<int> s;
然后就是开始遍历
for (int i = 0; i < size; i++) {
while (!s.empty() && heights[s.top()] > heights[i]) {
int h = heights[s.top()];
s.pop();
result = max(result, h * (s.empty() ? i : (i - s.top() - 1)));
}
s.push(i);
}
当栈顶元素比遍历到的边大时,则开始弹出栈顶元素,并开始计算以该条边作为高的矩形面积(因为在栈中是一个单调递增的情况,然后又遇到比自己短的边,故直接弹出计算),那么这个矩形的底如何求呢?这里又分两种情况:
s.empty() ? i : (i - s.top() - 1)
如果栈已经空了,那么说明此时以栈顶为高的矩形的底就是i(我们压入栈中的是下标,而不是元素值),否则底边就是i - s.top() - 1(为什么还要再减1呢,因为栈顶元素已经弹出来了,此时已经后移了一个位置),完整代码如下:
int largestRectangleArea(vector<int>& heights) {
heights.push_back(0);
int size = heights.size();
int result = 0;
stack<int> s;
for (int i = 0; i < size; i++) {
while (!s.empty() && heights[s.top()] > heights[i]) {
int h = heights[s.top()];
s.pop();
result = max(result, h * (s.empty() ? i : (i - s.top() - 1)));
}
s.push(i);
}
return result;
}
};