题目
算法思路--单调队列的应用
单调队列存储的元素值,是从队首到队尾单调递增或单调递减的。
第一步:变量定义
const int N=1000010;
int a[N],q[N],hh,tt;
N
:数组a
和队列q
的最大长度。a
数组用来存储输入的数组。q
数组作为双端队列,用来存储数组a
的索引值。hh
和tt
分别是队列的头和尾的指针(即队列的下标)。
第二步:读入数据
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;
}