开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第28天,点击查看活动详情
单调栈
如何维护一个单调栈:
单调递增栈:在保持栈内元素单调递增的前提下(如果栈顶元素大于要入栈的元素,将将其弹出),将新元素入栈
单调递减栈:在保持栈内元素单调递减的前提下(如果栈顶元素小于要入栈的元素,则将其弹出),将新元素入栈
单调栈的规律:
单调栈的时间复杂度是O(n)
- 对于单调递增栈:
对于将要入栈的元素来说,在对栈进行更新后(即弹出了所有比自己大的元素),此时栈顶元素就是数组中左侧第一个比自己小的元素;
- 对于单调递减栈
对于将要入栈的元素来说,在对栈进行更新后(即弹出了所有比自己小的元素),此时栈顶元素就是数组中左侧第一个比自己大的元素
什么时候使用单调栈
给定一个序列,求序列中的每一个数左边或右边第一个比他大或比他小的数在什么地方
输出每个数左边第一个比它小的数
做法:如果当前数比栈顶小,因为它更靠右,所以栈顶就没用了,所以
- 当前数<=栈顶元素,弹出栈顶元素, 不断处理,直到栈为空 或者 当前数> 栈顶元素
- 如果栈为空,那么当前数的左边第一个比它小的数就不存在,它本身就是当前[0,i]范围元素的最小值
- 如果当前数> 栈顶元素 ,那么此时的栈顶元素就是左边第一个比当前元素小的数
例子:给定一个长度为 N的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出-1
暴力做法:
for(int i = 0;i<n;i++)
{
for(int j = i-1;j>=0;j--) .//找的是a[i]左侧比它小的,所以从i-1位置开始往前找
{
if(a[i] > a[j]) cout << a[j] << endl; break;
}
}
单调栈的做法:
当该元素可以入栈的时候,所以我们只需要维护一个从栈顶到栈底单调递减的栈,每次不符合单调性就弹栈,此时栈顶元素就是它左侧第一个比它小的元素,
#include <iostream>
#include <stack>
using namespace std;
int n;
stack <int> st;
int main ()
{
cin >> n;
for (int i = 0;i < n;i++)
{
int x;
cin >> x;//当前元素
//如果栈不为空&&栈顶元素>=当前元素,那么就不满足栈是单调递增的,弹出栈顶元素
//维护从栈底到栈顶是单调递增的栈
while (!st.empty () && st.top () >= x) st.pop ();
//此时栈顶元素就是它左侧第一个比它小的元素,
if (!st.empty ()) cout << st.top () << ' ';
else cout << -1 << ' '; //栈为空,说明x右边没有任何一个数比x小
st.push (x);//把当前数入栈
}
return 0;
}
用数组实现栈:
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt; //tt:栈顶元素位置,最初为0 tt!=0 表示栈不为空
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
scanf("%d", &x);
while (tt && stk[tt] >= x) tt -- ;
if (!tt) printf("-1 ");
else printf("%d ", stk[tt]);
//tt代表的就是栈顶元素,所以新插入的元素是放在tt+1位置
stk[ ++ tt] = x;
}
return 0;
}
注意:此时相等的时候也要弹出栈顶元素,因为是找第一个小的值,不是第一个不大于的值
如果栈顶定义为-1位置:
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt = -1; //tt:栈顶元素位置,最初为0 tt!=0 表示栈不为空
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
scanf("%d", &x);
while (tt != -1 && stk[tt] >= x) tt -- ;
if (tt ==-1) printf("-1 ");
else printf("%d ", stk[tt]);
//tt代表的就是栈顶元素,所以新插入的元素是放在tt+1位置
stk[ ++ tt] = x;
}
return 0;
}