开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第30天,点击查看活动详情
单调队列
常见模型:找出滑动窗口中的最大值/最小值
例子:
我们从数组中第一个元素开始遍历,假设窗口的大小是3,因此当遍历到第三个元素时,窗口就形成了
之后,继续遍历元素时,为了保持窗口的大小为3,左侧元素就需要从窗口中剔除。这样使得窗口一直在向右移动,直到考察到最后一个元素结束,这就是所谓的滑动窗口
以求滑动窗口的最大值为例子:
- 此时要维护单调递减的单调队列
1)如果当前的滑动窗口中有两个下标 i 和 j ,其中i在j的左侧(i<j)并且 nums[i]≤nums[j],当滑动窗口向右移动时,只要 i 还在窗口中,那么 j 一定也还在窗口中, 这是由于 i 在 j 的左侧所保证的,因此,由于 nums[j] 的存在,nums[i] 一定不会是滑动窗口中的最大值了,我们可以将nums[i]永久地移除
2)因此我们可以使用一个队列存储所有还没有被移除的下标,在队列中,这些下标按照从小到大的顺序被存储,并且它们在数组nums中对应的值是严格单调递减的
3)当滑动窗口向右移动时,我们需要把一个新的元素放入队列中,为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果新元素大于等于队尾元素,那么队尾的元素就可以被永久地移除,我们将其弹出队列,我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素
4)由于队列中下标对应的元素是严格单调递减的,因此此时队头下标对应的元素就是滑动窗口中的最大值
- 注意:队列中存的不是值,而是下标,所以我们能考察当前队头的下标是否在窗口内
5)窗口向右移动的时候。因此我们还需要不断从队首弹出元素保证队列中的所有元素都是窗口中的,因此当队头元素在窗口的左边的时候,弹出队头
实例:假设现在求的是窗口的最大值:窗口内前面的数是3,后面的数是5, 3<=5, 前面的数会先出队列,后面的数后出队列, 只要前面一个数在,后面一个数肯定在, 并且 3<=5,5是作为窗口内的最大值,作为答案,所以前面的数可以删掉
所以队列中如果前面一个数比后面一个数小,那么前面这个数就是删掉, 这个队列就是单调下降的
求滑动窗口的最小值同理:
- 维护单调递增的单调队列
只要我的当前的这个数比队列前面那个数大,那么前面那个数就是没有用的,因为我当前数是后被弹出的,而且比前面那个数小大我才是作为答案的,所以只要有这样逆序的过程,就可以把小的点删掉,把这样的逆序对都删掉的话,整个的队列就变成严格的单调递增的队列,每一次找最小值就是找队头的元素
#include <iostream>
using namespace std;
const int N = 1000010;
int a[N], q[N];
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
//求滑动窗口的最小值 ->维护单调递增的队列,每次队头就是最小的
int hh = 0, tt = -1; //hh:队头 tt:队尾
//窗口大小为k,当前元素为i, 所以窗口的元素范围是:[i-k+1,i] -> 元素个数为k个
for (int i = 0; i < n; i ++ )
{
//判断当前队列是否为空&& 当前队头元素是否出了窗口
//这里之所以不写while,是因为每次窗口只会往后移动一位,每次队列里面最多只有一个数不在窗口
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
//注意:队列里面存的是下标 q[tt]:队尾元素 a[q[tt]]:队尾元素在a数组对应的值
//如果新插入的数比队尾的元素小,那么就把队尾弹出去,我们要保证队列是单调递增的
while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
//把当前元素放到队列当中,因为可能刚好把队列弹空,当前元素就是窗口的最小值
q[ ++ tt] = i; //注意:是先++tt, 因为tt初始化是-1,++tt队尾的位置
//当元素个数不足k个的时候,不需要打印,窗口还没有形成
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");
//求滑动窗口的最大值 -》 维护单调递减的队列
hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
q[ ++ tt] = i;
if (i >= k - 1) printf("%d ", a[q[hh]]);
}
puts("");
return 0;
}