C++ STL deque(双端队列)->单调队列 使用方法

首先声明 : 本人纯小白 如有错误 敬请各位大佬指正

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]最小值133333最大值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)

单调队列能干嘛呢? 为啥能用单调队列做? 当时在学习单调队列的时候 我也抱着这样的一个疑惑 ,那这就要从单调队列的性质说起了.

首先 单调队列有如下特征:
  1. 队首元素要么是最小的要么是最大的 取决于你构建的单调性 (这与优先队列不同,单调队列不改变元素的相对位置).
  2. 元素只能从队尾进入队列 ,从队尾,对首 都可以弹出.
  3. 序列中的每个元素都要进一次队列
就拿本题中求最小值的问题来说:
  1. 首先第一步 构建单调队列 题目要我们求最小值 那队首要最小 那就要构建单调递增队列
  2. 第二步 入队 若入队的元素 < 当前队尾元素 那就不满足单调性了 那就把前面比它大的元素全弹出去 再把当前元素插入
  3. 第三步 队列长度超过窗口总长度 弹出元素 输出当前窗口最小值

最大值 也是同样的操作方法

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;
}
  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值