洛谷 P1886:滑动窗口 ← 单调队列(STL queue)
【题目来源】
https://www.luogu.com.cn/problem/P1886
https://www.acwing.com/problem/content/156/
【题目描述】
给定一个大小为 n≤10^6 的数组。
有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 k 个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为 [1 3 -1 -3 5 3 6 7],k 为 3 。
窗口位置 | 最小值 | 最大值 |
[1 3 -1] -3 5 3 6 7 | -1 | 3 |
1 [3 -1 -3] 5 3 6 7 | -3 | 3 |
1 3 [-1 -3 5] 3 6 7 | -3 | 5 |
1 3 -1 [-3 5 3] 6 7 | -3 | 5 |
1 3 -1 -3 [5 3 6] 7 | 3 | 6 |
1 3 -1 -3 5 [3 6 7] | 3 | 7 |
你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
【输入格式】
输入包含两行。
第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。
第二行有 n 个整数,代表数组的具体数值。
同行数据之间用空格隔开。
【输出格式】
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。
【输入样例】
8 3
1 3 -1 -3 5 3 6 7
【输出样例】
-1 -3 -3 -3 3 3
3 3 5 5 6 7
【算法分析】
● 这是一道“单调队列”模板题。可以使用 STL deque 实现。在 C++ 标准模板库(STL)中,deque(double-ended queue,双端队列)是一个非常重要的容器,它支持在序列的两端进行快速插入和删除操作。所以,对于需要在两端进行修改的数据结构,例如单调队列,deque是一个理想的选择。STL deque:https://cplusplus.com/reference/deque/
● 在“单调队列”相关问题中,主要用到的 STL deque 中的函数如下所示:
front():https://cplusplus.com/reference/deque/deque/front/
back():https://cplusplus.com/reference/deque/deque/back/
push_back():https://cplusplus.com/reference/deque/deque/push_back/
push_front():https://cplusplus.com/reference/deque/deque/push_front/
pop_back():https://cplusplus.com/reference/deque/deque/pop_back/
pop_front():https://cplusplus.com/reference/deque/deque/pop_front/
● 单调队列应用于“滑动窗口”问题时确立相关元素下标的示意图(https://blog.csdn.net/hnjzsyjyj/article/details/144599941)
(1)若“滑动窗口”大小为 k,给定的数组各元素下标从 0 开始,则据上图可得“滑动窗口”左端元素下标 ≤i-k 时才能满足“滑动窗口”中的元素个数 >k,此时才能依据题意出队“滑动窗口”中的队首元素。另外,由于数组元素下标从 0 开始,所以当 i≥k-1 时,“滑动窗口”中的元素个数才能满足 ≥k 的约束条件,此时才能依据题意输出“滑动窗口”中队首元素的值。
(2)若“滑动窗口”大小为 k,给定的数组各元素下标从 1 开始,则据上图可得“滑动窗口”左端元素下标 <i-k+1 时才能满足“滑动窗口”中的元素个数 >k,此时才能依据题意出队“滑动窗口”中的队首元素。另外,由于数组元素下标从 1 开始,所以当 i≥k 时,“滑动窗口”中的元素个数才能满足 ≥k 的约束条件,此时才能依据题意输出“滑动窗口”中队首元素的值。
(3)实践证明,不管给定的数组各元素下标从 0 开始还是从 1 开始,判定大小为 k 的“滑动窗口”中的元素个数>k 的条件,可统一为“滑动窗口”左端元素下标 ≤i-k。
【算法代码一:from 0 + STL queue】
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int a[maxn];
typedef pair<int,int> PII;
deque<PII> q;
int n,k;
void minque() {
for(int i=0; i<n; i++) {
while(!q.empty() && q.front().first<=i-k) q.pop_front();
while(!q.empty() && q.back().second>a[i]) q.pop_back();
q.push_back({i,a[i]});
if(i>=k-1) cout<<q.front().second<<" ";
}
}
void maxque() {
for(int i=0; i<n; i++) {
while(!q.empty() && q.front().first<=i-k) q.pop_front();
while(!q.empty() && q.back().second<a[i]) q.pop_back();
q.push_back({i,a[i]});
if(i>=k-1) cout<<q.front().second<<" ";
}
}
int main() {
cin>>n>>k;
for(int i=0; i<n; i++) cin>>a[i];
minque();
q.clear(); //very important
cout<<endl;
maxque();
return 0;
}
/*
in:
8 3
1 3 -1 -3 5 3 6 7
out:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
*/
【算法代码二:from 1 + STL queue】
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int a[maxn];
typedef pair<int,int> PII;
deque<PII> q;
int n,k;
void minque() {
for(int i=1; i<=n; i++) {
while(!q.empty() && q.front().first<=i-k) q.pop_front(); //<i-k+1
while(!q.empty() && q.back().second>a[i]) q.pop_back();
q.push_back({i,a[i]});
if(i>=k) cout<<q.front().second<<" ";
}
}
void maxque() {
for(int i=1; i<=n; i++) {
while(!q.empty() && q.front().first<=i-k) q.pop_front(); //<i-k+1
while(!q.empty() && q.back().second<a[i]) q.pop_back();
q.push_back({i,a[i]});
if(i>=k) cout<<q.front().second<<" ";
}
}
int main() {
cin>>n>>k;
for(int i=1; i<=n; i++) cin>>a[i];
minque();
q.clear(); //very important
cout<<endl;
maxque();
return 0;
}
/*
in:
8 3
1 3 -1 -3 5 3 6 7
out:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
*/
【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/109680717
https://zhuanlan.zhihu.com/p/437438743
https://www.acwing.com/solution/content/6564/
https://www.acwing.com/file_system/file/content/whole/index/content/11525369/