滑动窗口——单调队列(C/C++)

一: 问题介绍

在这里插入图片描述

二: 问题分析——分析最大值举例

 1. 一般求解

   毫无疑问的,按照我们的传统思维暴力解决这一类问题,我们可以直接对k大小的窗口进行一个扫描和记录即可得到窗口内的最大值或者最小值的结果。但其时间复杂度往往是我们不可以接受的,因为每次扫描k次,若一共进行n次扫描,这个方法的时间复杂度是n*k的,在最坏的情况下这个时间复杂度可能达到O(n * n) ,这就是最常规的暴力求解思路。

 2. 分析优化

  a. 分析:
  我们首先直接选取一个一般的情况来分析找规律。假设窗口内的数字此时为 [ 5 7 3 4 6 2 8 1 3]. 假设我们此时要找窗口内的最大值。
  此时最大值假设为指针 j 指向,可以发现最大值会指向数字 8 . 我们可以发现找最大值的时候我们第一次会扫描 5 7 3 4 6 2 8,第二次当窗口右移的时候又会重复扫描 7 3 4 6 2 8 得到这一段的最大值为8 ,其实这就已经导致重复扫描了,于是我们想到能不能将重复扫描的部分直接剔除用最大数字来代表这一段
在这里插入图片描述

  结论:当队列当中存在 i < j 并且 num[ i ] <num[ j ]的时候,可以发现只要 i 在窗口内的时候 j 也一定在窗口当中,并且 num[ j ]的值永远可以替代掉num[ i ]的值,即num[i] 其实并不需要进行保存。
在这里插入图片描述

  b. 优化:
  因此,在窗口当中我们只要保存一个单调递减的数列即可,因为如果后面的数字比前面的数字更大的话,那么前面的数字可以直接删去由后面的数字进行代替。

三: 代码求解

 1. 代码解析

  a. 保存数列方式:使用deque< int >处理
  原因很简单,因为这个双头队列可以方便我们插入的时候可以快速利用pop_back()处理前面比我们小的数字。当窗口右移的时候也可以如果需要也可以使用pop_front()快速删除前面移出窗口的数字。
  b. 数字入队列代码:
  保证单调下降队列,所以先清空前面比我小的数字,再将入队列的数字进行插入。

	while(!q.empty()&&a[i]>q.back()) q.pop_back();  //先删除前面更小的数字,保证单调递减
    q.push_back(a[i]); // 再将当前数字加入队列

  c. 数字出队列代码:
  当窗口向右移动的时候,会有数字被移出窗口内,此时我们就需要判断移出的数字是否再窗口内,如果在我们就pop_front(),移出这个数字。

	//i 代表当前加入的数字的序号 ,a[]代表数字序列
	//当i大于K的时候,窗口最前面的数字就会被移出窗口此时就需要进行判断
	if(i>=k+1) if(q.front()==a[i-k]) q.pop_front(); 

  d. 答案:最大值就为单调队列里面的第一个数字了,
  例如上面最后优化的结果 5 7 3 4 6 2 8 1 3 被优化为 8 3,最大值也就显然为8了。因为8前面数字都不会比它更大,而后面的数字进入的时候没有把8移除证明后面的数字也不会有8大,于是当前窗口内的最大值就是第一个数字了。

// i>=k :首先窗口要进入k个数字后才能再输出结果
if(i>=k) cout<<q.front()<<" ";

 2. 完整代码+运算结果

  a. 完整代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int N=1e6+10;
deque<int> q;
int a[N];

int main(){
    int n,k;
    cin>>n>>k; // 输入 8 3
    for(int i=1;i<=n;i++) cin>>a[i];// 1 3 -1 -3 5 3 6 7
    
    cout<<"最小值结果为:";
    // 找窗口内的最小值,原理相似保证单调上升队列即可
    for(int i=1;i<=n;i++){
    	//保证单调上升
        while(!q.empty()&&a[i]<q.back()) q.pop_back(); 
        q.push_back(a[i]);
        if(i>=k+1) if(q.front()==a[i-k]) q.pop_front(); 
        if(i>=k) cout<<q.front()<<" ";
    }
    
    puts("");
    q.clear();
    
    cout<<"最大值结果为:";
    // 找窗口内的最大值
    for(int i=1;i<=n;i++){
        while(!q.empty()&&a[i]>q.back()) q.pop_back(); 
        q.push_back(a[i]);
        if(i>=k+1) if(q.front()==a[i-k]) q.pop_front(); 
        if(i>=k) cout<<q.front()<<" ";
    }

}

  b. 运行结果:
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
单调队列(Monotonic Queue)是一种数据结构,它可以用来解决一类滑动窗口问题。滑动窗口问题是指在一个长度为n的数组中,长度为k的窗口从左往右移动,每次移动一位,求出每个窗口的最大值最小值单调队列的思想就是维护一个单调递增或递减的队列,队列中的元素从队头到队尾是单调递增或递减的。当窗口向右移动时,先将队头元素出队,然后将新的元素从队尾入队,保持队列的单调性。这样就可以实现每个窗口的最大值最小值的求解。 下面是单调队列求解滑动窗口最大值C++代码: ```c++ vector<int> maxSlidingWindow(vector<int>& nums, int k) { vector<int> res; deque<int> dq; for (int i = 0; i < nums.size(); i++) { // 如果队列中的元素个数超过了窗口大小,就将队头元素出队 if (!dq.empty() && dq.front() == i - k) { dq.pop_front(); } // 将队列中比要加入的元素小的元素全部出队 while (!dq.empty() && nums[dq.back()] < nums[i]) { dq.pop_back(); } // 将新元素加入队列 dq.push_back(i); // 取当前窗口的最大值 if (i >= k - 1) { res.push_back(nums[dq.front()]); } } return res; } ``` 在上述代码中,使用双端队列deque来实现单调队列。dq.front()表示队头元素的下标,dq.back()表示队尾元素的下标。当队列为空时,dq.front()和dq.back()都是未定义的。 时间复杂度:O(n),每个元素最多被插入和删除一次。 空间复杂度:O(k),队列中最多存储k个元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

psudd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值