一 单调栈
单调栈就是栈内元素满足单调性的栈结构。此处的单调性分为单调递增与单调递减
如何维护一个单调栈:
单调递增栈:在保持栈内元素单调递增的前提下(如果栈顶元素大于要入栈的元素,将将其弹出),将新元素入栈。
单调递减栈:在保持栈内元素单调递减的前提下(如果栈顶元素小于要入栈的元素,则将其弹出),将新元素入栈。
什么时候使用单调栈:
给定一个序列,求序列中的每一个数左边/右边第一个比他小/比他大的数;
给定一个序列,求序列中的每一个数左边/右边第一个比他小/比他大的数在什么地方;
注:寻找位置时(下标时)stk栈存储的是下标,而不是数值
1.单调递增栈
单调递增栈应用:从左往右遍历——可以找到第一个比它小的元素的位置
单调递增栈就是栈内元素满足单调递增,假设当前元素为 x ,若栈顶元素 < x ,则将x 入栈,否则(栈顶元素 >= x)不断弹出栈顶元素,直至栈顶元素 < x。
(1)单调递增栈求左边第一个比它小的数
求序列中每一个数左边第一个比它小的数
for(int i = 1; i <= n; i ++)
{
int x;
cin >> x;
while(tt != 0 && stk[tt] >= x) tt --;
if(tt == 0) cout << "-1" << " ";
else cout << stk[tt] << " ";
stk[++ tt] = x;
}
(2)单调递增栈求左边第一个比它小的数的位置**
求序列中每一个数左边第一个比它小的数的位置
stack<int> s;
for(int i = 1; i <= n; ++i)
{
while(s.size() && a[s.top()] >= a[i]) s.pop();
if(s.empty()) l[i] = 0;
else l[i] = s.top();
s.push(i);
}
// 数组模拟栈
for(int i = 1; i <= n ; i ++ )
{
while(tt != 0 && a[stk[tt]] >= a[i]) tt--;
if(tt == 0) l[i] = 0; // 栈为空,不存在则记为0
else l[i] = stk[tt]; // 符合要求l[i]记录对应栈顶下标
stk[++ tt] = i; // 最后当前扫描到的下标入栈
}
2.单调递减栈
单调递减栈应用:从右往左遍历————寻找左边边第一个比当前元素大的数/数的位置
(1)单调递减栈求左边第一个比它大的数
求序列中每一个数左边第一个比它小的数
for(int i = 1; i <= n; i ++)
{
int x;
cin >> x;
while(tt != 0 && stk[tt] <= x) tt --;
if(tt == 0) cout << "-1" << " ";
else cout << stk[tt] << " ";
stk[++ tt] = x;
}
(2)单调递减栈求左边第一个比它大的数的位置
// 单调递减栈:从右往左遍历————寻找右边第一个比当前元素大的数的位置
for(int i = n; i >= 0; i -- )
{
while(tt != 0 && a[stk[tt]] <= a[i]) tt--;
if(tt == 0) l[i] = 0; // 栈为空,不存在则记为0
else l[i] = stk[tt]; // 符合要求l[i]记录对应栈顶下标
stk[++ tt] = i; // 最后当前扫描到的下标入栈
}
二 单调队列(单调双端队列)
int q[maxn],front=1,rear=0;
说单调队列,那我们就先说说这个单调队列是个什么物种。单调队列从字面上看,无非就是有某种单调性的队列,没错,这就是所谓的单调队列。 单调队列它分两种,一种是单调递增的,另外一种是单调递减的。
在这搬出百度百科的解释:不断地向缓存数组里读入元素,也不时地去掉最老的元素,不定期的询问当前缓存数组里的最小的元素。
用单调队列来解决问题,一般都是需要得到当前的某个范围内(连续的字序)的最小值或最大值。
队列中元素之间的关系具有单调性,且队首和队尾都可以进行出队操作,只有队尾可以进行入队操作。
用处:
对于维护好的单调队列,对内元素是有序的,那么取出最大值(最小值)的复杂度为O(1)
可以拿来优化DP(…)
单调队列:通俗的讲就是以一个固定长度的窗口在数列上移动,(这个固定的长度是题目给的,队首元素在队列中的位置与队尾元素在队列的位置之差若大于这个固定长度,就得将队首元素弹出)