又是没听懂课,看题解理解的内容
单调队列典型例题–滑动窗口
确定滑动窗口位于每个位置时,窗口中的最大值和最小值
暴力做法:
for(int i=0;i<n;i++) n次
for(int j=i;j>=i-k+1;j--) k次
{
res=min(a[i-k+1~i]);
}
时间复杂度O(nk)
思路:
1.解决队头出窗口的问题
2.解决队尾a[q[tt]]与当前元素a[i]不满足单调性的问题
3.将当前元素下标加入队尾
4.若满足条件,输出
注意:
1.上面四个步骤中一定要先3后4,因为有可能输出的正是新加入的那个元素;
2.队列中存的是原数组的下标,取值时要再套一层,a[q[]];
3.算最大值前注意将hh和tt重置;
4.此题用cout会超时,只能用printf;
5.hh从0开始,数组下标也要从0开始。
代码
using namespace std;
const int N=1e6+10;
int a[N],q[N];
int n,k;
int main()
{
cin>>n>>k;
int hh=0,tt=-1;
//最小值
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
//解决队头出滑动窗口的问题 hh++
if(q[hh]<i-k+1) hh++;
//解决队尾元素和当前元素a[i]不满足单调性 tt-- //(加进来的数小,队尾就不好使了)
while(hh<=tt && a[q[tt]]>=a[i]) tt--; //最终维护的队列是个单调增序列
//当前元素的下标插入队尾
q[++tt]=i;
//若满足条件:至少循环到第k-1轮,才能有输出
if(i>=k-1) printf("%d ",a[q[hh]]);
}
cout<<endl;
hh=0,tt=-1;
//最大值
for(int i=0;i<n;i++)
{
if(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;
}
我们就是用一个队列来维护一个序列,以求最小值为例
到第从i=k-1 到 i=n-1 ,每次都需要输出一个滑动窗口的最小值
就需要从队头到队尾维护一个单调递增的序列
只要新加入的数要小于队尾的数,就需要一直从队尾让数据出队(和传统队列有出入,这是双向队列)
直到形成一个单调递增的序列
同时要保证队尾和队头囊括的元素个数为k,这就需要在每一次循环前判断是否 q[hh]<i-k+1 如果< hh++
也就是说队列数组最多用到了k个位置