单调栈
AcWing 830. 单调栈
1、暴力做法:外层循环枚举每一个元素 x,内层循环找到这个 x 的左边第一个比它小的数。在求 x 的左边第一个比它小的数,是将 x 左边所有的数全部入栈,从栈顶开始找到第一个比它小的数,找到后停下来。
2、在暴力做法下进行优化。我们发现,如果 a1 >= a2,那么当找 x 的左边第一个比它小的数时, 如果 i < j,且 ai >= aj,ai 一定不是答案,那么 ai 是可以删掉的。删除后栈内元素就是一个单调的序列了,所以叫单调栈。
为了维护这个单调性质,我们在插入 x 前,循环判断如果栈顶元素 >= x,栈顶元素出栈即可,这样最后的栈顶元素就是 < x 的左边第一个数了。
#include<iostream>
using namespace std;
const int N = 1e5+10;
int stk[N], n;
int tt;
int main()
{
cin >> n;
for(int i=0; i<n; i++)
{
int x;
cin >> x;
while(tt && stk[tt] >= x) tt--; //栈顶元素出栈
if(tt) cout << stk[tt] <<" ";
else cout <<"-1 ";
stk[++tt] = x; //x入栈
}
}
我们发现每个元素最多只能进栈一次,也最多只能出栈一次,所有元素最多执行 2*N 次,时间复杂度O(N)
单调队列
AcWing 154. 滑动窗口
1、暴力做法:遍历队列中的 k 个值,总共需要 O(N*k) ,超时。
2、在暴力的基础上优化。我们发现,在当前窗口,如果 i < j,且 ai >= aj,那么 ai 是永远不含当作最小值的,我们可以直接将 ai 删掉,此时的队列元素就是一个单调递增的序列了,因此叫单调队列。
为了维护单调这个性质,我们在队尾插入 x 之前,循环判断如果队尾元素 >= x,将队尾元素删除即可。这样队头元素就是当前队列的最小值了。
#include<iostream>
using namespace std;
const int N = 1e6+10;
int n,k;
int a[N], q[N];
int main()
{
cin.tie(0);
cin >> n >> k;
for(int i=1; i<=n; i++) cin >> a[i];
int hh = 0, tt = -1;
for(int i=1; i<=n; i++)
{
//判断队头是否需要滑出窗口,保证窗口内有 k 个元素
if(hh <= tt && i-k+1 > q[hh]) hh++; //队头出队
while(hh <= tt && a[q[tt]] >= a[i]) tt--; //删除队尾
q[++tt] = i; //x入队
if(i >= k) cout<< a[q[hh]] << " "; //数大于 k 个才需输出
}
cout << endl;
hh = 0, tt = -1;
for(int i=1; i<=n; i++)
{
if(hh <= tt && i-k+1 > q[hh]) hh++;
while(hh <= tt && a[q[tt]] <= a[i]) tt--;
q[++tt] = i;
if(i >= k) cout<< a[q[hh]] << " ";
}
}
由于每个元素最多进队一次,出队一次,所有元素最多执行 2*N 次,时间复杂度O(N)