zoukankan      html  css  js  c++  java
  • 单调队列

    滑动窗口

    /**
     * 暴力做法
     */
    #include <iostream>
    #include <limits.h>
    
    using namespace std;
    
    const int N = 1e6 + 10;
    
    int n, k;
    int a[N];
    int vec_max[N], vec_min[N], tt_max, tt_min;
    
    int main()
    {
        cin >> n >> k;
        for (int i = 0; i < n; ++ i) cin >> a[i];
        for (int i = 0; i <= n - k; ++ i)
        {
            int maxx = INT_MIN, minn = INT_MAX;
            for (int j = i; j < i + k; ++ j)
            {
                maxx = max(maxx, a[j]);
                minn = min(minn, a[j]);
            }
            vec_max[tt_max ++] = maxx;
            vec_min[tt_min ++] = minn;
        }
        for (int i = 0; i < tt_min; ++ i) cout << vec_min[i] << ' ';
        cout << endl;
        for (int i = 0; i < tt_max; ++ i) cout << vec_max[i] << ' ';
        return 0;
    }
    
    /**
     * 正解
     * 步骤是先考虑暴力的做法,然后找到没有用的元素,比如在单调栈”距离最近的较小值“中无用的元素就是相较于当前值前面的较大值,因为它们一定更不会作为后面数据的答案,所以去掉即可
     * 本题的滑动的窗口的实现形式和队列非常相似,窗口的每次滑动都是队头弹出一个元素,队尾添加一个元素
     * 考虑在找最小值时,1 3 -1 -3 4,当3 -1 -3处于同一个窗口时,3和-1对于后面新添加的数据来说一定不会是选择的最小值,因为有-3的存在,选定的最小值就一定不会是它们,所以当-3出现后,对于后面的元素,完全没有必要再去考虑3和-1,他们也就是无用元素
     * 考虑最大值时,-3 -1 3当3出现后,对于后面的数据,因为3的存在,-3和-1显然都不会是答案,所以它们也就成为了无用元素
     * 这道题对我来说的难点在于很难想到分别去求最小值和最大值,因为我想到的就是同时求,如果没有想到分别求,那就很难想到这样的优化了,说起来不算困难,但是代码实现初步感觉有点混乱,不知道怎么去实现
     *
     * 以求最小值为例,每个数据都会进入队列,在数据m进入后,队列中已有元素中>=m的元素就一定不会是答案了,所以要先把这些数从队列中去掉,这也就完成了上面说到的去除重复元素的任务,之后再把m放进队列即可
     * 大体的思路是这样的,但是很多细节还需要注意
     * 队列要存放下标而非数值,原因是队头一定是窗口内最小元素(因为每次我们都把更大的元素弹出去了),如果能取到这个元素的前提是这个元素还在窗口内部,也就是我们需要能够定位元素位置,如果队列存储数组我们是无法定位的,所以存储下标
     *
     * 程序写完之后,感觉到正解就是用单调队列把暴力中找最小值的过程从o(N)降到了o(1)
     * 队列维护的在原数组的窗口内要么是当前的最小值,要么可能是之后的最小值的数,原数组窗口中那些保证不会是答案的数全部都被删除了
     * 而且不好理解的就是窗口向后移动时,离开窗口的数据未必是队列中的,它可能属于那些保证不会是答案的数
     */
     #include <iostream>
     
     using namespace std;
     
     const int N = 1e6 + 10;
     
     int n, k, a[N];
     int hh, tt, q[N];
     
     int main()
     {
         cin >> n >> k;
         for (int i = 0; i < n; ++ i) cin >>a[i];
         
         // 求窗口内最小值
         hh = 0, tt = -1; // 数组模拟队列时提到的初始化方式,不一定非要这么写,把细节处理好即可
         for (int i = 0; i < n; ++ i) 
         {
             // 窗口向后滑动一位,有原有数组中新的数据进入窗口
             while (hh <= tt && a[q[tt]] >= a[i]) -- tt; // 把前面更大的数据弹出队列,它们在后续的滑动过程中一定不会作为答案,hh <= tt的必要性在于需要保证q[tt]下标的合法性
             q[++ tt] = i;
             
             // 窗口向后滑动一位,有原有数组中一些数据要离开窗口
             // 此时有两种情况,离开窗口的是我们已经排除的不可能是答案的数据,也可能是当前的最小值,只有后者才是在队列中的,所以我们要做的就是判断队头元素是不是要离开窗口了,此时队列存储下标再一次派上了用场
             if (hh <= tt && q[hh] < i - k + 1) ++ hh; // a1 a2 a3 a4,如果k是3,当i到3(a4)时,那么窗口的左端点下标应该为1(a2),即3 - 3 + 1(i - k + 1),删除头节点的前提是队列不为空,即 hh <= tt
             
             if (i >= k - 1) cout << a[q[hh]] << ' '; // 从满足长度为3的节点开始输出答案
         }
    
        cout << endl;
    
        // 求窗口内最大值, 和最小值对应一下即可
         hh = 0, tt = -1;
         for (int i = 0; i < n; ++ i) 
         {
             while (hh <= tt && a[q[tt]] <= a[i]) -- tt;
             q[++ tt] = i;
             
             if (hh <= tt && q[hh] < i - k + 1) ++ hh;
    
             if (i >= k - 1) cout << a[q[hh]] << ' ';
         }
    
         return 0;
     }
    
  • 相关阅读:
    ABP.NET 方法使用 HTTPPOST,HTTPGET,HTTPPUT 特性
    Asp.Net Core 获取配置系统的链接字符串
    ABP.NET CORE 框架 取消新增用户邮箱地址必填验证
    前端开发常用组件库
    微信RSA加密公钥API
    动手实现一个较为简单的MQTT服务端和客户端
    计算入职时间的时间算法
    Windows Terminal 安装及美化
    Windows10内置Linux子系统(WSL)安装
    分享powershell设定网卡,ip,网关,dns的命令
  • 原文地址:https://www.cnblogs.com/G-H-Y/p/14299722.html
Copyright © 2011-2022 走看看