滑动窗口 c++

文章讲述了如何在给定数组中,使用滑动窗口的概念寻找每个位置上窗口内的最大值和最小值。提出了两种解决方案,一种是每次滑动窗口时重新遍历窗口内的元素来更新最大值和最小值,另一种是利用双端队列优化,保持队列内元素顺序以快速找到最大值和最小值,从而提高效率。
摘要由CSDN通过智能技术生成

题目

给定一个大小为 n≤10^6≤10^6 的数组。

有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 k 个数字。

每次滑动窗口向右移动一个位置。

以下是一个例子:

该数组为 [1 3 -1 -3 5 3 6 7],k为 3。

窗口位置最小值最大值
[1 3 -1] -3 5 3 6 7-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式

输入包含两行。

第一行包含两个整数 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

思路1

一个简单的思路,就是在每一个滑动窗口中,遍历一次滑动窗口中的数,找到滑动窗口中的最大值和最小值,并用两个数组把它们存起来。若数组长度为n,滑动窗口的长度k,则有n-k+1个滑动窗口,每个窗口遍历k个数,时间复杂度O(nk)。

那有什么可以减少比较的次数吗?我们发现每次滑动窗口时,只有一个数发生了改变,即原滑动窗口左端元素被推出,而窗口右端进入一个新元素。如果标记原窗口最大最小值及其位置,那么在新的窗口中,若最大最小值仍在新窗口中,则只需要拿新元素和原来的最大最小值比较并更新,若最大或最小值不在新窗口中,我们再遍历一次窗口。时间复杂度在O(n)~O(nk)之间。

代码

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 +10;
typedef pair<int, int> PII;

int  ma[N], a[N];//a[]存储输入数组,ma[]存每个窗口最大值
PII lmi, lma;//临时保存的最大最小值及其位置

//判断最大最小值是否仍在窗口
bool check_out(int st) {
    if (st > lmi.second || st > lma.second) return true;//不在时
    return false;
}

//查找最大最小值
void refind(int st, int en) {
        for (int i = st; i <= en; i++) {
        if (lmi.first >= a[i]) {
            lmi.first = a[i];
            lmi.second = i;
        }
        if (lma.first <= a[i]) {
            lma.first = a[i];
            lma.second = i;
        }
    }
}


int main () {
    lmi.first = INT_MAX, lmi.second = 0,lma.first = INT_MIN, lma.second = 0;
    int n, k;
    scanf("%d%d", &n, &k);
    
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    
    int st = 1, en = k;
    while (st <= (n - k + 1) && en <= n) {
        while (check_out(st) && st <= (n - k + 1) && en <= n) {//最大或最小值不在窗口
            lmi.first = INT_MAX,lma.first = INT_MIN;
            refind(st, en);
            printf("%d ",lmi.first);
            ma[st] = lma.first;
            st++, en++;
        }
        while (!check_out(st) && st <= (n - k + 1) && en <= n) {
            refind(en ,en);
            printf("%d ",lmi.first);
            ma[st] = lma.first;
            st++, en++;
        }
    }
    
      printf("\n");
    for (int i = 1; i < st; i++) {
        printf("%d ", ma[i]);
    }
    
    return 0;
}

思路2

从上面思路1中,上一个窗口的最大或最小值不在新窗口中时,我们依旧遍历一次窗口。原因是我们并不知道第二大或第二小的值在什么地方。

下面我以找最小值为例。

我们能否再定义一个双端队列,里面保存窗口中元素的下标,下标的排序具有这个特点:下标对应的数组元素从小到大排序。比如样例中的窗口[1,3,-1],取出下标后在队列中的排序是下面这样的。

我们为什么要这样子做呢?当我们想要窗口最值时,可以从队头中获得。而当最小值不在新窗口中时,即队列中第一个元素对应的数组元素不在新窗口时,我们把队头元素推出,这时新的队头对应的数组元素再先新元素比较,如果新元素不是最小值,就把它的下标放到队列的适当位置。

但貌似插入操作也是挺费时的,我们能否找到一个性质,使得队列元素对应的数组元素从小到大排序,并且不需要插入呢?我们创建队列的目的,就是当上一个窗口的最小值不在新窗口中时,减少查找新的最小值的次数。我们发现,当新元素比原来窗口的一些元素要小时,那么这些元素就是没有用的元素,因为它们的位置在新元素之前,会比新元素提前被推出滑动窗口,同时由于新元素比这些元素要小,这些元素永远不会被当做最小值输出。这一规律反映到队列中,就是把这些数组元素的下标从队列推出删除。

那我们怎么知道什么时候该输出队头元素对应在数组中的元素呢?由于队列元素为滑动窗口经过的前面的元素中选择,并且队头永远是滑动窗口最小的,每次当窗口要滑动时,我们就可以输出。


怎么判断窗口滑动呢?用for 循环遍历一遍数组a[],当i >= k - 1(i从 0 开始)时每次输出队头元素。

代码

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;
int a[N];
//int hh = 0, ee = -1;, q[N]
deque<int> q;

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    
    for (int i = 0; i < n; i++) {
        if (!q.empty() && q.front() < i - k + 1)q.pop_front();
        while(!q.empty() && a[q.back()] >= a[i]) q.pop_back();
        q.push_back(i);
        if (i >= k - 1) printf("%d ", a[q.front()]);
    }
    puts("");
    
    q.clear();
    for (int i = 0; i < n; i++) {
        if (!q.empty() && q.front() < i - k + 1)q.pop_front();
        while(!q.empty() && a[q.back()] <= a[i]) q.pop_back();
        q.push_back(i);
        if (i >= k - 1) printf("%d ", a[q.front()]);
    }
    return 0;
}

自定义双端队列,速度更快。

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 +10;
int a[N], q[N];
int hh = 0, tt = -1;

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    
    for (int i = 0; i < n; i++) {
        if (hh <= tt && q[hh] < i - k + 1) hh++;//判断最小值是否还在滑动窗口中,不在就推出元素
        while (hh <= tt && a[q[tt]] >= a[i]) tt--;//当队尾元素对应a[]中的元素比滑动窗口的最后一个元素大,推出
        q[++tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    puts("");

    hh = 0, tt = -1;
    for (int i = 0; i < n; i++) {
        if (hh <= tt && q[hh] < i - k + 1) hh++;//判断最小值是否还在滑动窗口中
        while (hh <= tt && a[q[tt]] <= a[i]) tt--;//队头放最大的元素下标
        q[++tt] = i;
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
单调队列(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个元素。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值