单调栈,顾名思义,就是一个栈中的数据是具有单调性的(从小到大、从大到小)。
上次写单调队列时,我用了一句话来描述单调队列:
当一个人比你小还比你强,你就该考虑退役了 ! \large\text{当一个人比你小还比你强,你就该考虑退役了 !} 当一个人比你小还比你强,你就该考虑退役了 !
− W h e n s o m e o n e i s y o u n g e r t h a n y o u a n d b e t t e r t h a n y o u , i t ′ s t i m e t o c o n s i d e r r e t i r e m e n t ! − \small{-\ When\ someone\ is\ younger\ than\ you\ and\ better\ than\ you,\ it's\ time\ to\ consider\ retirement!\ -} − When someone is younger than you and better than you, it′s time to consider retirement! −
(详见 \footnotesize\text{(详见} (详见 单调队列 \footnotesize\text{单调队列} 单调队列 ) \footnotesize) )
现在要写单调栈,其实这句话也适用于单调栈,而他两的区别就在于:
-
你可以把单调队列看作一个定区间,队列里的数据必须存在于这段区间内,通过队首(极值)得出答案。
-
而单调栈一般是在新数据入栈时,与出栈的数据进行相处理,最后综合处理过程得出答案。
注:与单调队列一样,单调栈存的也是下标
伪代码:
stack<int> st;
for (遍历这个数组){
while (栈不为空 && 栈顶元素比当前元素弱){
栈顶元素出栈;
更新结果;
}
当前数据入栈;
}
得出结果
光看伪代码,大部分人都会说,我会写,但我该怎么用呢?
请结合题目食用:
题目描述:有 n 个人排成一队,每个人都是向右看的,高的人可以看到矮的人的发型,求所有人能看到其他人发型总和是多少。
输入:
4
4 3 7 1
输出:
2
通过观察,我们可以发现实际上题目转化为:找当前数字向右查找的第一个大于他的数字之间有多少个数字,最后在累加起来即可。
朴素算法: O ( n 2 ) O(n^2) O(n2)
这题我们只需要知道每个数字右边第一个大于他的数字在哪就行了,而且每个数字找到比它大的数字后就不再用得着了。因此我们考虑使用单调栈。
代码:
stack<int>st;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
a[n+1]=INT_MAX;
for(int i=1;i<=n+1;i++){
while(!st.empty()&&a[st.top()]<=a[i]){
int t=st.top();
st.pop();
ans+=(i-t-1);
}
st.push(i);
}
cout<<ans;
这个代码的核心部分就在 while
循环里:
把新数字压入栈中时,如果他比当前对比的值大,那么他就是这个对比值右边第一个比对比值大的数。别忘记单调栈里存的是下标,,此时已知两者下标,求出中间有几个数字便轻而易举了。然后再把当前对比值弹出(反正不会再用到它了)。
看到这个代码,想必有的同学会问:“为什么要在队尾放个无限大?”
因为要想知道总和,必须保证每个数字右边的数量我们都知道,既然如此,就必须使得最后每个数都被弹出栈。那么最后设置一个无限大,这样就可以使得每个数字都被弹出。
最后用个 a n s ans ans 累加一下就行了。
小结:
单调栈并不难,只是需要结合实际情况,确定入栈顺序和结果的更新即可。