主要思路:构建一个队列,把每个元素入队,维护队列的单调性,同时要兼顾队列里面的元素是紧凑不多余并且在滑动窗口内
这样每次队头元素取出来就是当前滑动窗口的MAX/MIN
假设求滑动窗口最小值MIN,并且窗口是从左向右移动,从左到右扫描a[]
第一步:
在将a[i]入队之前需要注意先将队列中超出滑动窗口的元素去除掉,这样可以保证在队列中的元素一
定是当前窗口内的元素。
由于队列的特性先进先出,上一次窗口中最靠左的元素下标是 (i-1) - k + 1,
而且队列中的元素都是窗口中的元素,最左边的元素一定是最先进入队列的,
如果这个元素在队列中一定是q[hh]记录这个下标
移动窗口就会把这个元素移出窗口,那这个元素就不应该留在队列中
如果这个元素不在队列中,那么就不需要这一步,因为移动窗口其余元素不会出窗口
所以进行判断if(cond1 && cond2) hh++;
其中cond1 = hh<=tt 表示队列非空
cond2 = q[hh] == (i-1) - m + 1
化简一下cond2 = (q[hh] == i-m);
hh++表示队头出队。
总而言之队头元素只要是上个窗口左边界元素的下标那么只要移动窗口就一定会出界
我自己写这个代码时候在想能否从队尾移出出界元素下标?
在想明白之后感觉这个问题相当xx,但还是想吐槽一下
如果队尾出界说明窗口大小=1,
否则队尾永远不会出界,队尾表示上一个窗口最右边的元素下标,那么这次窗口只要大于1
一次移动不足以将队尾移出窗口.
当窗口=1时候队列里面一定有元素并且只有一个,那么队头队尾一致(hh==tt),当窗口向右移动时候
队尾元素存储的下标i-1一定小于窗口左边界i,判断队尾还是等于判断队头是否超出左边界
第二步:
移出超出窗口元素之后就是维护队列的单调性
为什么要从y总从队尾出元素维护队列单调性?
假设队列里面有-3 4 5 现在我要进3
从队头开始判断那么-3不用出队,4,5需要出队,说明从4开始往后的元素都要出队,
如果从后往前出队写起来就会很方便,当队尾元素小于入队元素时候说明a[i]入队是可以继续保持单调性的
如果不从队尾出队维护单调性,也可以用中间变量p记录哪个位置开始a[q[p]]会在插入a[i]破坏单调性
即a[q[p]]>=a[i]说明从p开始到tt队列中这部分元素是需要出队的那么令tt = p-1即可
第三步:
把a[i]入队
第四步
然后输出
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e6+10;
const int M = ( 1e6 + 10 ) * 2;
int n,m,a[N],hh,tt,q[M];
int main(){
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
hh = 0;tt = -1;
//从左向右扫描
for(int i=0;i<n;i++){
//当上一个窗口左边界元素下标(i-1)-m+1还在队列中一定是q[hh],检查并移除滑动窗口外的元素
if(hh<=tt && q[hh] == i-m) hh++;
//if(hh<=tt && q[hh] < i-m+1) hh++;
//从队头判断找到破坏单调性的位置
int p = hh;
while(p<=tt&&a[q[p]] < a[i]) p++;
//然后修改tt位置
tt = p-1;
//入队
q[++tt] = i;
//输出
if(i+1 >= m) cout<<a[q[hh]]<<" ";
}
cout<<endl;
hh = 0;tt = -1;
for(int i=0;i<n;i++){
if(hh<=tt && q[hh] < i-m+1) hh++;;
//从队尾出队维护单调性显然更加简洁
while(hh<=tt && a[q[tt]] <= a[i]) tt--;
q[++tt] = i;
if(i+1 >= m) cout<<a[q[hh]]<<" ";
}
cout<<endl;
return 0;
}