题目描述
有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
例如:
输入格式
输入一共有两行,第一行有两个正整数 n,k 第二行 n 个整数,表示序列 a
输出格式
输出共两行,第一行为每次窗口滑动的最小值
第二行为每次窗口滑动的最大值
输入输出样例
输入 #1
8 3 1 3 -1 -3 5 3 6 7
输出 #1
-1 -3 -3 -3 3 3 3 3 5 5 6 7
说明/提示
【数据范围】
对于 50% 的数据,1≤n≤10^5 ;
对于 100% 的数据,1≤k≤10^6,ai∈[−2^31,2^31)。
一开始的想法很简单,每次都遍历一遍窗口,然而窗口有这么多个,理所当然的也就超时了,哪怕用上了O2吸氧照样还是超时一个点,虽然对于其他点来说快了很多
#include <bits/stdc++.h>
using namespace std;
int n,k,a[1000000],s;
queue<int>minn,maxx;
int main(){
cin>>n>>k,k--;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
while (s+k<n){
int minnEle=INT_MAX,maxxEle=INT_MIN;
for(int i=s;i<=s+k;i++){
minnEle=minnEle<a[i]?minnEle:a[i];
maxxEle=maxxEle>a[i]?maxxEle:a[i];
}
minn.push(minnEle);
maxx.push(maxxEle);
s++;
}
while(!minn.empty()){
cout<<minn.front()<<" ";
minn.pop();
}cout<<endl;
while(!maxx.empty()){
cout<<maxx.front()<<" ";
maxx.pop();
}
return 0;
}
那么有没有一种办法能让遍历这么多次窗口,变成只遍历一次原序列a呢?
核心思想:对于一个窗口和接下来的窗口,他们需要的只是最小值,所以大的就不需要了。
维护一个单调队列和一个与之对应的下标队列。
单调队列:
一个一个加入数据
1.维护队列的单调性,这样输出队头或者队尾就是需要的最大值或者最小值
2.维护入队数值下标的合理性,保证下标范围在窗口范围内
3.如果下标在窗口范围内输出队列的最值。
AC代码
#include <bits/stdc++.h>
using namespace std;
int n,k,a[1000001];
int monotoQ[1000001],monotoSubscriptQ[1000001];
void entMin(){
int head=1,tail=0;
for(int i=0;i<n;i++){
while(head<=tail&&monotoQ[tail]>a[i])tail--;
monotoQ[++tail]=a[i];
monotoSubscriptQ[tail]=i;
while(monotoSubscriptQ[head]<i-k+1)head++;
if(i>=k-1)cout<<monotoQ[head]<<" ";
}
}
void entMax(){
int head=1,tail=0;
for(int i=0;i<n;i++){
while(head<=tail&&monotoQ[tail]<a[i])tail--;
monotoQ[++tail]=a[i];
monotoSubscriptQ[tail]=i;
while(monotoSubscriptQ[head]<i-k+1)head++;
if(i>=k-1)cout<<monotoQ[head]<<" ";
}
}
int main(){
cin>>n>>k;
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
entMin();
cout<<endl;
entMax();
return 0;
}
AC代码(STL版)
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e6+1;
int n,k,a[MAXN];
void entmax(){
deque<pair<int,int>>q;
for(int i=1;i<=n;i++){
while(!q.empty()&&q.back().second<a[i])q.pop_back();
q.push_back({i,a[i]});
while(!q.empty()&&q.front().first<=i-k)q.pop_front();
if(i>=k)cout<<q.front().second<<" ";
}
}
void entmin(){
//维护降序队列321
deque<pair<int,int>>q;//下标,数据
for(int i=1;i<=n;i++){
//队列back值大于加入的值,那就不需要了
while(!q.empty()&&q.back().second>a[i])q.pop_back();
q.push_back({i,a[i]});
//队列的下标超时了,那就不需要了,最早的是front
while(!q.empty()&&q.front().first<=i-k)q.pop_front();
//满足窗口范围
if(i>=k)cout<<q.front().second<<" ";
}
}
int main(){
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
entmin();
cout<<endl;
entmax();
return 0;
}
可以发现STL还是慢了很多的。
维护队列而不是维护一个pair<int,int>下标+数据是因为单个值会发生跳值的现象。谁试谁知道