首先声明 : 本人纯小白 如有错误 敬请各位大佬指正
std::deque 是 C++ STL 库 提供的一种 双端队列 其常用成员函数有:
empty()
: 判断队列是否为空
size()
: 队列元素个数
push_back(值)
: 队尾插入元素
push_front(值)
: 队首插入元素
pop_front()
:弹出对头元素
pop_back()
:弹出队尾元素
back()
:返回队尾元素的值(引用返回)
front()
:返回对首元素的值(引用返回)
常见应用:
有了上面的一些常用操作,做题也就够用了 ,deque的一种经典应用就是滑动窗口(slide window)
直接上题目:
原题链接:滑动窗口
题目大意: 给你n 个元素和一个 长度为 k 的窗口 窗口从第一个元素开始向后移动 要求输出每次窗口中的最小值和最大值
例如,对于序列 [ 1 , 3 , − 1 , − 3 , 5 , 3 , 6 , 7 ] [1,3,-1,-3,5,3,6,7] [1,3,−1,−3,5,3,6,7] 以及 k = 3 k = 3 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 \def\arraystretch{1.2} \begin{array}{|c|c|c|}\hline \textsf{窗口位置} & \textsf{最小值} & \textsf{最大值} \\ \hline \verb![1 3 -1] -3 5 3 6 7 ! & -1 & 3 \\ \hline \verb! 1 [3 -1 -3] 5 3 6 7 ! & -3 & 3 \\ \hline \verb! 1 3 [-1 -3 5] 3 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 [-3 5 3] 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 -3 [5 3 6] 7 ! & 3 & 6 \\ \hline \verb! 1 3 -1 -3 5 [3 6 7]! & 3 & 7 \\ \hline \end{array} 窗口位置[1 3 -1] -3 5 3 6 7 1 [3 -1 -3] 5 3 6 7 1 3 [-1 -3 5] 3 6 7 1 3 -1 [-3 5 3] 6 7 1 3 -1 -3 [5 3 6] 7 1 3 -1 -3 5 [3 6 7]最小值−1−3−3−333最大值335567
暴力做法 : 双指针
时间复杂度 O ( k n ) O(kn) O(kn)
从前向后扫,每次扫描 k k k个单位 然后算出最大值和最小值存到数组里最后再输出
代码如下:
只有两个测试集没过
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define int long long
using namespace std;
const int N = 1e6+10;
int a[N];//原数组
int n,k;
vector<int> rmax,rmin; //答案数组
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i]; //输入数据不用多说
//接下来从前向后扫描 慢指针i 最多到 n-k+1
//因为算上i自己还要向后扫描k-1个单位
for(int i=1;i<=n-k+1;i++){
int j =i;
int min_=0x3f3f3f3f;//正无穷 这里换成 a[i] 也是可以的
int max_=-0x3f3f3f3f;//负无穷
//j是快指针 i是慢指针 他俩的距离开始0
//因为j要向后移动k-1个单位 他俩距离一定要小于等于k-1
while(j-i<=k-1){
//每次取最小值和最大值
min_ = min(min_,a[j]);
max_ = max(max_,a[j]);
j++;
}
rmax.push_back(max_);
rmin.push_back(min_);
}
//输出答案
for(auto &x:rmin){
cout<<x<<" \n"[&x==&rmin.back()];
}
for(auto &x:rmax){
cout<<x<<" \n"[&x==&rmax.back()];
}
}
正解: 单调队列
时间复杂度 O ( n ) O(n) O(n)
单调队列能干嘛呢? 为啥能用单调队列做? 当时在学习单调队列的时候 我也抱着这样的一个疑惑 ,那这就要从单调队列的性质说起了.
首先 单调队列有如下特征:
- 队首元素要么是最小的要么是最大的 取决于你构建的单调性 (这与优先队列不同,单调队列不改变元素的相对位置).
- 元素只能从队尾进入队列 ,从队尾,对首 都可以弹出.
- 序列中的每个元素都要进一次队列
就拿本题中求最小值的问题来说:
- 首先第一步 构建单调队列 题目要我们求最小值 那队首要最小 那就要构建单调递增队列
- 第二步 入队 若入队的元素 < 当前队尾元素 那就不满足单调性了 那就把前面比它大的元素全弹出去 再把当前元素插入
- 第三步 队列长度超过窗口总长度 弹出元素 输出当前窗口最小值
最大值 也是同样的操作方法
AC 代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 1e6+10;
int a[N];//原数组
deque<int> q; //创建一个双端队列 ,注意:q存的是下标 因为便于计算窗口的长度
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
//输入不必多说
int n,k;cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
//求最小值
for(int i=1;i<=n;i++){
//这里 队列为空就入队
//若不为空 看队尾元素是否大于被插入元素
//队尾元素就不断地弹出 直到比它小为止
while(q.size()&&a[q.back()]>a[i]) q.pop_back();
q.push_back(i);
// 如果我要输出最小值 那窗口中总得大于等于k个元素吧
if(i>=k){
//q.front() 是当前队首元素的下标
//比如说 k=3 当前 i=4 1 +k==i 那么下标为 1的元素就是脱离窗口的 就要弹出
if(q.size()&&q.front()+k<=i) q.pop_front();
//现在窗口中有k个元素 队首是最小值 直接输出即可
cout<<a[q.front()]<<" ";
}
}
cout<<"\n";
while(!q.empty())q.pop_front();
for(int i=1;i<=n;i++){
while(q.size()&&a[q.back()]<a[i]) q.pop_back();
q.push_back(i);
if(i>=k){
if(q.size()&&q.front()+k<=i) q.pop_front();
cout<<a[q.front()]<<" ";
}
}
return 0;
}