AcWing 154. 滑动窗口

题目

算法思路--单调队列的应用

单调队列存储的元素值,是从队首到队尾单调递增或单调递减的。

第一步:变量定义

const int N=1000010;
int a[N],q[N],hh,tt;
  • N :数组 a 和队列 q 的最大长度。
  • a 数组用来存储输入的数组。
  • q 数组作为双端队列,用来存储数组 a索引值。
  • hhtt 分别是队列的头和尾的指针(即队列的下标)。

第二步:读入数据

int n,k;
scanf("%d %d",&n,&k);
int hh=0,tt=-1;
for(int i=0;i<n;i++) cin>>a[i];

n 表示数组 a 的长度,k 表示滑动窗口的大小。

第三步:滑动窗口最小值计算部分

for(int i=0;i<n;i++)
{
    if(hh<=tt&&i-k+1>q[hh]) hh++; // 队头元素滑出窗口
    while(hh<=tt&&a[i]<a[q[tt]]) tt--; // 弹出队尾元素
    q[++tt]=i;
    
    if(i>=k-1) cout<<a[q[hh]]<<" ";
}
printf("\n");

判断队头是否需要滑出窗口

hh <= tt:检查队列是否为空

i-k+1 > q[hh]:检查队列中队头的元素是否已经滑出当前窗口。i-k+1 是当前窗口的起始索引,而 q[hh] 是队列中队头元素的索引。如果 q[hh] 小于 i-k+1,说明这个元素已经不在当前窗口内。

hh++:队头指针 hh 向右移动一位,将其移出队列。

维护单调递增队列

a[i] < a[q[tt]]:检查当前元素 a[i] 是否小于队尾元素 a[q[tt]]q[tt] 是队列中队尾元素的索引。

如果 a[i] 小于队尾元素 a[q[tt]],则弹出队尾元素,即将 tt 指针向左移动一位,tt--,直至a[i]成为队列中最大的元素。将其添加到队尾

输出当前窗口的最小值

i >= k-1:表示当前已经遍历过了至少 k 个元素,形成窗口大小,此时可以开始输出滑动窗口的最小值。

由于队列中存储的是递增顺序的索引,队头元素 a[q[hh]] 就是当前窗口的最小值。

第四步:滑动窗口最大值计算部分

hh=0;tt=-1;
for(int i=0;i<n;i++)
{
    if(q[hh]<i-k+1) hh++; // 队头元素滑出窗口
    while(hh<=tt&&a[i]>a[q[tt]]) tt--; // 弹出队尾元素
    q[++tt]=i;
    
    if(i>=k-1) cout<<a[q[hh]]<<" ";
}

和之前的最小值部分逻辑相似,只是将条件从 a[i] < a[q[tt]] 改为了 a[i] > a[q[tt]],目的是维护一个单调递减的队列,从而计算每个窗口的最大值。此时的队头元素是最大的。

示例(弹出最小值为例)

输入为

数组 a = [2, 1, 5, 6, 2, 3]
窗口大小 k = 3。

i = 0 (元素 a[0] = 2)

  • 队列为空,直接将 a[0] 的索引 0 加入队列。
  • q 变为 [0]
  • 窗口没有满 (i < k-1),不输出。

i = 1 (元素 a[1] = 1)

  • a[1] < a[q[tt]] (1 < 2),弹出队尾元素。
  • 将索引 1 加入队列。
  • q 变为 [1]
  • 窗口没有满 (i < k-1),不输出。

i = 2 (元素 a[2] = 5)

  • a[2] > a[q[tt]] (5 > 1),直接将索引 2 加入队列。
  • q 变为 [1, 2]
  • 窗口满了 (i >= k-1),输出 a[q[hh]] = a[1] = 1

i = 3 (元素 a[3] = 6)

  • q[hh] = 1,窗口范围内 (i-k+1 <= q[hh])。
  • a[3] > a[q[tt]] (6 > 5),直接将索引 3 加入队列。
  • q 变为 [1, 2, 3]
  • 窗口满了 (i >= k-1),输出 a[q[hh]] = a[1] = 1

i = 4 (元素 a[4] = 2)

  • q[hh] = 1 超出窗口范围 (i-k+1 > q[hh]),弹出队头元素。
  • 弹出队尾元素 a[4] < a[q[tt]] (2 < 6)。
  • 弹出队尾元素 a[4] < a[q[tt]] (2 < 5)。
  • 将索引 4 加入队列。
  • q 变为 [4]
  • 窗口满了 (i >= k-1),输出 a[q[hh]] = a[4] = 2

i = 5 (元素 a[5] = 3)

  • q[hh] = 4,窗口范围内 (i-k+1 <= q[hh])。
  • a[5] > a[q[tt]] (3 > 2),直接将索引 5 加入队列。
  • q 变为 [4, 5]
  • 窗口满了 (i >= k-1),输出 a[q[hh]] = a[4] = 2

窗口最小值的输出序列为:1, 1, 2, 2,时间复杂度为O(n)

总代码

#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int a[N],q[N],hh,tt;
int main()
{
    int n,k;
    scanf("%d %d",&n,&k);
    int hh=0,tt=-1;
    for(int i=0;i<n;i++) cin>>a[i];
    for(int i=0;i<n;i++)
    {
        if(hh<=tt&&i-k+1>q[hh]) hh++;//队头元素滑出窗口;
        while(hh<=tt&&a[i]<a[q[tt]]) tt--;//弹出队尾元素
        q[++tt]=i;
        
        if(i>=k-1) cout<<a[q[hh]]<<" ";
    }
    printf("\n");
    hh=0;tt=-1;
    for(int i=0;i<n;i++)
    {
        if(q[hh]<i-k+1) hh++;//队头元素滑出窗口;
        while(hh<=tt&&a[i]>a[q[tt]]) tt--;//弹出队尾元素
        q[++tt]=i;
        
        if(i>=k-1) cout<<a[q[hh]]<<" ";
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值