zoukankan      html  css  js  c++  java
  • 单调队列的原理及其实现

    单调队列是一种用来快速查询一段长度为n的数组的一部分的最大最小值的算法。它原理相对简单,且易于实现,并且时间复杂度是接近O(n)的,可以解决很多问题。下面以洛谷P1886滑动窗口 /【模板】单调队列为例来介绍单调队列的实现方法。

    先放上题目:

    题目描述

    有一个长为n的序列a,以及一个大小为k的窗口。现在这个窗口从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

    例如:

    The array is [1,3,-1,-3,5,3,6,7],and k=3。

    输入格式

    输入一共有两行,第一行有两个正整数n,k。第二行n个整数,表示序列a

    输出格式

    输出共两行,第一行为每次窗口滑动的最小值
    第二行为每次窗口滑动的最大值

    解法介绍:

    单调队列并不使用queue,而只需要在原序列中维护两个边界,分别为l和r,代表研究的部分的左边界位置、右边界位置。我们将左边界的位置初始化为1,右边界的位置初始化为k,代表最初这个研究部分只包含a[1]到a[k]这些节点。此时,我们并不知道这个子区间的最大值和最小值分别是多少,所以我们需要维护一个find函数来寻找子区间内的最大、最小值。这里我们进行一次find初始化,并且对最初的最大值、最小值进行记录。find函数只需要从l到r进行枚举查询即可。

    find函数代码实现如下:

     

    1 const int inf=0x3f3f3f3f;
    2 void find(int l,int r){
    3     maxn=-inf;minn=inf;//初始化最大值和最小值
    4     for(int i=l;i<=r;i++){
    5         maxn=max(maxn,a[i]);
    6         minn=min(minn,a[i]);
    7     }
    8 }

    接下来我们需要对这个窗口进行滑动操作。我们总共需要向右移动(n-k+1)次,每一次都要进行l++,r++的操作。而在操作的时候我们需要维护最值,这也是最为重要的操作

    我们在维护时要分很多种情况:

    第一种,也是最好处理的一种情况,当在进行l++操作之前的l所对应的a[l]既不是上一个窗口所对应的最大值,又不是它所对应的最小值时,删去这个值并不会影响窗口内的最大值和最小值,这时候我们可以放心的l++,r++,然后用新加入单调队列的a[r]对maxn和minn进行update更新即可。

    代码:

     1 void update(){
     2   l++;
     3   r++;
     4   maxn=max(maxn,a[r]);
     5   minn=min(minn,a[r]);
     6 }
     7 //接下来是在循环中的判断
     8 if(a[l]!=maxn&&a[l]!=minn){
     9   update();
    10 } 

    第二种,当在进行l++操作之前的l所对应的a[l]是上一个窗口所对应的最大值,而不是它所对应的最小值时,删除a[l]会对新区间内的最大值造成影响,我们已经不知道除了这个被删除掉的节点以外,新窗口的最大值是多少,所以我们要重新查找。但是,这种情况不会对最小值造成影响。但是,如果新加入的节点要大于等于要删除的节点,那么删除掉这个节点也就无所谓了。所以我们还需要加入一个判断:

    1 if(a[l]==maxn){//如果左边界的值等于最大值 
    2     if(a[r+1]>=a[l]){//新加入节点值大于等于删除的节点 
    3         update();//不需要重新查找 
    4     }else{
    5         l++;
    6         r++;
    7         find(l,r);//否则重新查找 
    8     }
    9 }

    第三种,当在进行l++操作之前的l所对应的a[l]是上一个窗口所对应的最小值,而不是它所对应的最大值时,我们可以用与第二种情况相同的方式来查找。代码如下:

    1 if(a[l]==minn){ 
    2   if(a[r+1]<a[l]){
    3       update();
    4   }else{
    5       l++;
    6       r++;
    7       find(l,r);
    8   }
    9 } 

    第四种,也是最为玄学的一种情况,就是当在进行l++操作之前的l所对应的a[l]既是上一个窗口所对应的最小值,又是它所对应的最大值的情况。这种情况下直接重新查找就可以了,毕竟这种情况极少,且考虑起来会很麻烦。

    代码:

    1 if(a[l]==maxn&&a[l]==minn){
    2     l++;
    3     r++;
    4     find(l,r);
    5 }

    最后附上完整代码:

     1 #include<iostream>
     2 #include<algorithm>
     3 using namespace std;
     4 const int inf=0x3f3f3f3f;
     5 int n,k; 
     6 int l,r;
     7 int a[1000005];
     8 int maxn,minn;
     9 int max_ans[1000005],min_ans[1000005];
    10 void record(int now){
    11     max_ans[now]=maxn;
    12     min_ans[now]=minn;
    13 }
    14 void find(int l,int r){
    15     maxn=-inf;
    16     minn=inf;
    17     for(int i=l;i<=r;i++){
    18         maxn=max(maxn,a[i]);
    19         minn=min(minn,a[i]);
    20     }
    21 }
    22 void update(){
    23     l++;
    24     r++;
    25     maxn=max(a[r],maxn);
    26     minn=min(a[r],minn);
    27 }
    28 int main(){
    29     cin>>n>>k;
    30     for(int i=1;i<=n;i++){
    31         cin>>a[i];
    32     }
    33     l=1;
    34     r=k;
    35     find(l,r);
    36     for(int i=1;i<=n-k+1;i++){
    37         record(i);
    38         if(a[l]!=maxn&&a[l]!=minn){
    39             update();
    40         }else if(a[l]==maxn&&a[l]==minn){
    41             l++;
    42             r++;
    43             find(l,r);
    44         }else if(a[l]==maxn){
    45             if(a[r+1]>a[l]){
    46                 update();
    47             }else{
    48                 l++;
    49                 r++;
    50                 find(l,r);
    51             }
    52         }else{
    53             if(a[r+1]<a[l]){
    54                 update();
    55             }else{
    56                 l++;
    57                 r++;
    58                 find(l,r);
    59             }
    60         }
    61     }
    62     for(int i=1;i<=n-k+1;i++){
    63         cout<<min_ans[i]<<' ';
    64     }
    65     cout<<endl;
    66     for(int i=1;i<=n-k+1;i++){
    67         cout<<max_ans[i]<<' ';
    68     }
    69     cout<<endl;
    70     return 0;
    71 }
  • 相关阅读:
    POJ
    归并排序+归并排序求逆序对(例题P1908)
    HDU
    2018-12-5 及 codeforces round 525v2
    2018-12-1学习纪录
    近期总结和未来规划
    C++ storage allocation + Dynamic memory allocation + setting limits + initializer list (1)
    注意项
    第四课 计算机的基本组成
    第二课+第三课 计算机系统概论
  • 原文地址:https://www.cnblogs.com/qianr/p/13821867.html
Copyright © 2011-2022 走看看