滑动窗口 c++

题目

给定一个大小为 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
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值