单调栈的含义
从名字上就听的出来,单调栈中存放的数据应该是有序的,所以单调栈也分为单调递增栈和单调递减栈
单调递增栈:数据进栈的序列为单调递增序列
单调递减栈:数据进栈的序列为单调递减序列
ps:这里一定要注意所说的递增递减指的是进栈的顺序,而不是在栈中数据的顺序,可能每个人的定义有所不同。
还有一点要注意,单调栈里通常存储的不是元素,而是元素的下标,不是单调栈里的元素是有序的,而是单调栈存的元素(也就是下标)对应的元素是有序的
例如:有数组[5,4,2],对应的单调栈为[0,1,2]出栈元素为 2,4,5
单调元素的伪代码
单调递减栈伪代码如下,相应的写出单调增栈
stack<int> st;
//此处一般需要给数组最后添加结束标志符,具体下面例题会有详细讲解
for (遍历这个数组)
{
if (栈空 || 当前比较元素比栈顶元素大)//根据要求在合适的地方添加等号
{
入栈;
}
else
{
while (栈不为空 && 当前元素比栈顶元素小或者等于)
{
栈顶元素出栈;//注意这里很多时候应是元素的下标
更新结果;
}
当前数据入栈;
}
}
用法:单调递减栈,通常用于在遇到比前面元素大的数据时候,要更新结果的情况;单调递增栈,通常用于遇到比前面元素小的数据时候,要更新结果的情况下。
为保证单调栈中元素全部弹出,对于要使用单调递减栈时,在原来的数组末尾添加一个极大值INT_MAX(或者其他比元素都大的数值);对于要使用单调递增栈时,在原来的数组末尾添加一个极小值INT_MIN(或者其他比元素都大的数值).
下笔如有神–练习
视野总和(单向扩展)
描叙:有n个人站队,所有的人全部向右看,个子高的可以看到个子低的发型,给出每个人的身高,问所有人能看到其他人发现总和是多少。
输入:4 3 7 1
输出:2
解释:个子为4的可以看到个子为3的发型,个子为7可以看到个子为1的身高,所以1+1=2
思路:暴力解法时间复杂度为O(N^2),
这是仔细观察题目,在遇到比自己大的元素的时候就需要更新结果所以我们使用单调递增栈来解决这个问题。
1.设置一个单调递增的栈(存储元素下标)
2.当遇到大于栈顶的元素,开始更新之前不高于当前人所能看到的值
int sumField(vector<int> height){
height.push_back(INT_MAX);//递增栈,加入一个极大值
stack<int> v;
int length = height.size();
int sum = 0;
int i=0;
for(;i<length;i++){
if(v.empty()||height[v.top()]>height[i]){
v.push(i);
}
else{
while(!v.empty()&&height[v.top()]<=height[i]){//注意等号
sum += i-v.top()-1;//两边都取不到
v.pop();//用了之后再弹出
}
v.push(i);//符合条件了就压入
}
}
return sum;
}
此问题是一个单向扩展的问题,比较简单,只需要单调栈中的top(),没有用到单调栈的top前面的元素,单调栈栈顶前面的元素表示了元素向左扩展的长度
柱状图中的最大矩形(双向扩展)
LeetCode的题目
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
思路:暴力法:从当前元素往下找,直到末尾,那么复杂度为n!,这个太暴力了;有个稍微暴力一些的,就是从当前的位置,向两边扩展,遇到比自己大的就持续扩展,遇到小的就停止更新,复杂度n2;
有第二个暴力思路可以联想到单调递增栈(遇到小就更新),注意这是个双向扩展的问题,这个时候就要用到单调栈的top()和top()前那个元素,单调栈栈顶前面的元素表示了左边第一个比栈顶元素小的元素,是可以向左扩展的边界
int largestRectangleArea(vector<int>& heights) {
heights.pusb_back(0);
int length = heights.size();
stack<int> h;
int sum = 0;
if(length==1) return sum;
for(int i=0;i<length;i++){
if(h.empty()||heights[i]>heights[h.top()]){//注意等号相等不能压入,最直接的反例就是全是相等
h.push(i);
}
else{
while(!h.empty()&&heights[h.top()]<=heights[i]){
int cur = h.top();//注意双向扩展
h.pop();
int leftCur = h.empty()?-1:h.top();//向左扩展
int temp = heights[cur]*(i-leftCur-1);//注意两个边界都取不到
sum = max(sum,temp);
}
h.push(i);//不要忘了入栈
}
}
return sum;
}
当然这道题应该也能用动态规划
总结
1、当遇到找第一个比自己大或者比自己小的问题时,可以考虑到单调栈;
2、遇到需要个边界问题时,也能考虑用单调栈。