单调栈
例题选自leetcode
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
什么是单调栈?
可以看看我以前的博客(刚刚大二的时候写的),可能稍微有点不成熟,姑且看之哈~
题目内容


算法分析
算法1:暴力
如果能够求出以每个包含小矩形 iii 的大矩形面积,然后维护全局最大值,那也是可以的!可是,如何求出第 iii 个小矩形所在的大矩形面积呢?只需要记录小矩形左侧第一个比它矮的位置,假设是lll,以及右侧第一个比它矮的位置,假设是rrr,那么它的面积就是:area=Hi∗(r−l−1)area=Hi*(r-l-1)area=Hi∗(r−l−1)
这样的代码是:
cpp
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int ans = 0, len = (int)heights.size();
for (int i = 0; i < len; ++i) {
int l = i, r = i;
while (l >= 0 && heights[l] >= heights[i])
--l;
while (r < len && heights[r] >= heights[i])
++r;
int area = heights[i] * (r - l - 1);
if (ans < area)
ans = area;
}
return ans;
}
};
这样能通过 87 / 96 个测试用例,当然,最后的结局是TLE!为何呢?因为它的复杂度是O(n2)O(n^2)O(n2)!
算法2:单调栈
为何要用单调栈?
我们还是要计算,第 iii 个小矩形最左侧,小于它的第一个元素在哪里?以及,大于它的第一个元素在哪里?
利用一个单调递增栈 ,假如现在来了一个元素,它比第 iii 个元素小,则这个元素必然是可以作为它右侧第一个 比它小的元素的!然后呢?我们是不是就需要找到第iii个矩形左侧第一个比它小的元素呢?怎么做呢?

假如我们现在,要计算第iii个矩形能延展到的左端点lll,怎么计算呢?大家想一想,既然是单调递增栈,那么lll到iii的矩形肯定都已经出栈了!所以呀,当前栈的下一个元素,就是lll的上一个元素!于是,程序可以这样写:
cpp
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
heights.insert(heights.begin(), 0);
heights.push_back(0);
int ans = 0, len = (int)heights.size();
stack<int> idxStk;
for (int i = 0; i < len; ++i) {
while (!idxStk.empty() && heights[i] < heights[idxStk.top()]) {
int H = heights[idxStk.top()];
idxStk.pop();
int area = H * (i - idxStk.top() - 1);
if (ans < area)
ans = area;
}
idxStk.push(i);
}
return ans;
}
};
哨兵思想
如果尾部,每一个比任何元素都小的元素作为哨兵,那么程序要稍微复杂一点!原因是,还需要处理荣誉在栈中的元素。因为,假设高度数组全是递增的,那么压根不会出现因为破坏单调性而需要出栈的元素,所以会留下冗余的元素弥留在栈中!
那么怎么办呢?可以考虑加一个一定会破坏单调性的元素,比如0,因为高度的最小值是0!它一定会破坏单调性!那么,为什么首部也需要加一个哨兵呢?原因是,在计算宽度的时候,如果没有哨兵,那么,栈为空和不为空的结果是不一样的!
如果栈为空,则当前元素是前iii个元素里的最小值,宽度是整个iii,否则是i−stk.top()−1i-stk.top()-1i−stk.top()−1,为了统一计算方法,我们可以给首部加一个哨兵!