滑动窗口模板
基本概念
滑动窗口是一种基于双指针的一种思想,两个指针指向的元素之间形成一个窗口。
分类:窗口有两类,一种是固定大小类的窗口,一类是大小动态变化的窗口。
什么情况可以用滑动窗口来解决实际问题呢?
- 一般给出的数据结构是数组或者字符串
- 求取某个子串或者子序列最长最短等最值问题或者求某个目标值时
- 该问题本身可以通过暴力求解
核心思路
一个简单的思路,就是在每一个滑动窗口中,遍历一次滑动窗口中的数,找到滑动窗口中的最大值和最小值,并用两个数组把它们存起来。若数组长度为n,滑动窗口的长度k,则有n-k+1个滑动窗口。
那有什么可以减少比较的次数吗?我们发现每次滑动窗口时,只有一个数发生了改变,即原滑动窗口左端元素被推出,而窗口右端进入一个新元素。如果标记原窗口最大最小值及其位置,那么在新的窗口中,若最大最小值仍在新窗口中,则只需要拿新元素和原来的最大最小值比较并更新,若最大或最小值不在新窗口中,我们再遍历一次窗口。
实现代码:
//滑动窗口的模板
#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=1e6+5;
int a[N],q[N];//定义两个数组,a数组用来存入数,q数组用来存放下标
int hh=0,tt=-1;
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n,k;
cin >> n >> k;
for(int i=0;i<n;++i) cin >> a[i];
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) cout << a[q[hh]] << " ";
}
cout << endl;
hh=0,tt=-1;
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) cout << a[q[hh]] << " ";
}
return 0;
}
如上代码是模拟双端队列实现的,时间复杂度比双端队列快了一倍。
双端队列的代码也奉上:
#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=1e6+5;
int a[N];
deque<int> q;
signed main(){
//ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n,k;
cin >> n >> k;
for(int i=0;i<n;++i) cin >> a[i];
for (int i = 0; i < n; i++) {
if (!q.empty() && q.front() < i - k + 1) q.pop_front();
while(!q.empty() && a[q.back()] >= a[i]) q.pop_back();
q.push_back(i);
if (i >= k - 1) printf("%d ", a[q.front()]);
}
cout << endl;
q.clear();
for (int i = 0; i < n; i++) {
if (!q.empty() && q.front() < i - k + 1)q.pop_front();
while(!q.empty() && a[q.back()] <= a[i]) q.pop_back();
q.push_back(i);
if (i >= k - 1) printf("%d ", a[q.front()]);
}
return 0;
}