• 单调队列---数据结构与算法


    简介

    队列也是一种受限制的线性表和栈相类似,栈是先进后出,而队列是先进先出,就好像一没有底的桶,往里面放东西,如图

    在这里也是用数组来实现队列,用数组实现的叫做顺序队列

    队列的数组模拟

    1. const int N = 1000010;
    2. //在队尾插入元素 队头弹出元素
    3. int q[N],hh,tt=-1; //hh代表队头 tt代表队尾
    4. //插入
    5. q[++tt] = x ;
    6. //弹出
    7. hh++ ;
    8. //判断队列是否为空
    9. if(hh <= tt ) not empty
    10. else empty
    11. //取出队头元素
    12. q[hh] ;

    单调队列

    单调队列也就是说其中的元素始终保持单调性

    常见应用:找出滑动窗口中的最大值/最小值

    如题:给定一个大小为 n ≤ 10^6 的数组。 有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。 你只能在窗口中看到 k 个数字。 每次滑动窗口向右移动一个位置。 以下是一个例子: 该数组为 [1 3 -1 -3 5 3 6 7],k 为 3。

     输入格式
    输入包含两行。
    第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。
    第二行有 n 个整数,代表数组的具体数值。
    同行数据之间用空格隔开。
     输出格式
    输出包含两个。
    第一行输出,从左至右,每个位置滑动窗口中的最小值。
    第二行输出,从左至右,每个位置滑动窗口中的最大值。
     输入样例

    1. 8 3
    2. 1 3 -1 -3 5 3 6 7

     输出样例
     

    1. -1 -3 -3 -3 3 3
    2. 3 3 5 5 6 7

    例子的输出解释: 

            窗口的位置               最小值                    最大值
    【1 3 -1】 -3 5 3 6 7                -1                3
    1【 3 -1 -3】 5 3 6 7                -3                3
    1 3 【-1 -3 5 】3 6 7                -3                5
        1 3 -1【 -3 5 3 】6 7                -3                5
        1 3 -1 -3 【5 3 6 】7                3                6
        1 3 -1 -3 5【 3 6 7】                3                7

    对于解开这题我们依旧尝试暴力做法

    既然求滑动窗口中的最大值和最小值,那我们只需要让窗口滑动 n - k + 1 次,再每次都对窗口里的k个数,求出最大/小值就可以了,以下是部分代码

    1. //求最小值
    2. for (int i = 0 ; i < n - k + 1 ; i++)
    3. {
    4. int min = -1;
    5. for (int j = i; j < i + k; j++)
    6. {
    7. if (a[i] <= min)
    8. {
    9. min = a[i];
    10. }
    11. }
    12. printf("%d ", min);
    13. }
    14. //求最大值
    15. //

     这样的做法不仅要遍历数组一遍,在这个过程中窗口还要遍历,相当于遍历了 n * k 遍,非常浪费时间。

    优化的方向也是和之前的单调栈类似,有很多元素根本就不可以在后边用到, 例如

    我们求最小值时,当 a [ x ] >= a [ y ] 并且 x > y ,这种情况就可以将 a [ x ] 从我们这个队列删除

    反之,易得。

    这就得到了队列的单调性

     对于这个循环中我们需要做3个步骤

    1. 检测队列是否为空 并且 队头是否滑出了窗口,这是什么意思呢?窗口的大小固定是 k ,我们要保证队列中的元素全是窗口里的数的下标,队头保存的下标如果是窗口左边的下标,就说明要将队头的元素移出队列(这里只需要判断一次,因为每次循环最多添加一个元素)

    2. 检测队列是否为空 并且队尾所指向的元素是否大于等于此时的元素,如果为真,要将队尾移出队列

    3.再将我们此时指向的元素的下标加入队列

    4.打印最小值,肯定不是每一次循环都需要打印,你会发现前 k - 1 次不需要打印,变成 i 的话就是i >= k - 1(i从0开始),因为此时窗口都没有满

    现在将以上步骤变成代码

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include <iostream>
    3. using namespace std;
    4. const int N = 1000010;
    5. int n, k;
    6. int a[N], q[N]; //q 数组是队列
    7. int hh, tt = -1; //hh是队头 tt是队尾
    8. int main(void)
    9. {
    10. scanf("%d%d", &n, &k);
    11. for (int i = 0; i < n; i++)
    12. {
    13. scanf("%d", &a[i]);
    14. }
    15. for (int i = 0; i < n; i++)
    16. {
    17. if (hh <= tt && q[hh] < i - k + 1) hh++; // i - k + 1 就是窗口的最左边的下标
    18. while (hh <= tt && a[q[tt]] >= a[i]) tt--;
    19. q[++tt] = i;
    20. if (i >= k - 1 ) printf("%d ", a[q[hh]]);
    21. }
    22. puts(""); //打印空字符串,虽然打印为空,但是使用 puts() 显示字符串时,系统会自动在其后添加一个换行符
    23. hh = 0, tt = -1; //记得要初始化数列
    24. for (int i = 0; i < n; i++)
    25. {
    26. if (hh <= tt && q[hh] < i - k + 1) hh++;
    27. while (hh <= tt && a[q[tt]] <= a[i]) tt--;
    28. q[++tt] = i;
    29. if (i >= k - 1) printf("%d ", a[q[hh]]);
    30. }
    31. puts("");
    32. return 0;
    33. }

    运行图

    成功得到结果

  • 相关阅读:
    Java并发编程学习十一:CAS
    《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(13)-Charles如何进行Mock和接口测试
    【go-zero】go-zero 脚手架 simple-admin 第二章:通过goctls生成api整个项目
    QT界面设计随笔
    FMI标准:实现SkyEye与Simulink无需缝合的联合仿真
    pytorch的安装【全官网流程】
    【二分】Pythagorean Triples—CF1487D
    Go语言学习笔记——Golang 1.18新特性工作区workspace
    计算摄影——图像对比度与色调增强
    【ubuntu18.04安装rabbitmq】
  • 原文地址:https://blog.csdn.net/qq_66805048/article/details/133562066