算法学习笔记(15)——单调队列

单调队列

单调队列算法很多时候用手写的模拟队列比较方便,因为很多时候需要双口出队的队列,主要是在队尾也有删除元素的需求。而模拟队列都是游标移动来限定队列中的所有元素,所以用模拟队列很自然的可以做到双端队列的操作。

单调队列一个经典应用就是求滑动窗口里的最大(或者最小)值。

算法步骤:

  1. 把该滑出的滑出(由于每次移动一步,最多也就滑出一个,所以一个if就行了,不需要用while
  2. 在入队前,看看队尾元素和新元素是不是破坏的单调性(也可以从“又老又差”这个角度去思考),不断从队尾删除(这里要用while了,因为可能删多个)
  3. 新元素入队
  4. 如果窗口已经达到 k 这么大了才需要输出结果(滑动窗口最值,即单调队列队头)
    这里解释一下第四步,是因为滑动窗口的大小是固定的 k ,所以第一个窗口的形成其实不是一开始就能形成的,因为一开始只加了一个元素进来,要加够 k 个元素才能形成窗口:

题目链接:AcWing 154. 滑动窗口

#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int n, k;
int q[N];   // 队列:存储数组元素对应的下标
int a[N];   // 数组

int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; i ++ ) cin >> a[i];
    
    // 初始化队列为空,hh队头指针,tt队尾指针
    int 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;
        // 当遍历至窗口内的元素个数达到k时,输出每次遍历到的窗口内的最小元素
        if (i >= k - 1) cout << 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) cout << a[q[hh]] << ' ';
    }
    puts("");
    
    return 0;
}

每个元素至多入队一次、出队一次,所以时间复杂度是O(N)。它的思想也是在决策集合(队列)中及时排除一定不是最优解的选择。单调队列也是优化动态规划的一个重要手段。

posted @   S!no  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示