AcWing 830. 单调栈
思路是让这题变成一个单调递增的数列,若新出现的一个数比前面的数小,那么就逐个覆盖。每次输出栈尾就是左边不大于他的第一个数。
#include<iostream> using namespace std; const int N =100010; int tt=0; int stk[N]; int main() { int n; 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; } return 0; }
AcWing .滑动窗口
这道题目的时间限制卡得比较紧,需要用 O(n)O(n) 时间复杂度的算法来做。
这是一道单调队列的模板题,以求最小值为例:
我们从左到右扫描整个序列,用一个队列来维护最近 kk 个元素;
如果用暴力来做,就是每次都遍历一遍队列中的所有元素,找出最小值即可,但这样时间复杂度就变成 O(nk)O(nk) 了;
然后我们可以发现一个性质:
如果队列中存在两个元素,满足 a[i] >= a[j] 且 i < j,那么无论在什么时候我们都不会取 a[i] 作为最小值了,所以可以直接将 a[i] 删掉;
此时队列中剩下的元素严格单调递增,所以队头就是整个队列中的最小值,可以用 O(1)O(1) 的时间找到;
为了维护队列的这个性质,我们在往队尾插入元素之前,先将队尾大于等于当前数的元素全部弹出即可;
这样所有数均只进队一次,出队一次,所以时间复杂度是 O(n)O(n) 的。
#include<iostream> using namespace std; int n,k; const int N =1000010; int a[N],q[N]; //q数组存储的是下标。 int main() { scanf("%d%d",&n,&k); for(int i=0;i<n;i++) scanf("%d",&a[i]); int hh=0,tt=-1; //hh表示队首,tt表示队尾。 for(int i=0;i<n;i++) { if(hh<=tt&&q[hh]<i-k+1)hh++; //如果满足题意的数列此时超过k个,需要让hh向前加1。 while(hh<=tt&&a[q[tt]]>=a[i])tt--;//如果队尾元素比新出现的元素小,那么他一定不是那个最小的值 q[++tt]=i;//存储下标 if(i>=k-1) printf("%d ",a[q[hh]]);//输出最小值 } hh=0,tt=-1; cout<<endl; for(int i=0;i<n;i++) { if(hh<=tt&&q[hh]<i-k+1)hh++; while(hh<=tt&&a[q[tt]]<=a[i])tt--; //最大值和最小值思路是一样的,将这里的大于号改成小于号,那么就是输出最大值。 q[++tt]=i; if(i>=k-1) printf("%d ",a[q[hh]]); } return 0; }