单调队列

问题引入-POJ2823

Description

An array of size n ≤ 106 is given to you. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves rightwards by one position. Following is an example: The array is [1 3 -1 -3 5 3 6 7], and k is 3.
Window positionMinimum valueMaximum value
[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

Your task is to determine the maximum and minimum values in the sliding window at each position.

Input

The input consists of two lines. The first line contains two integers n and k which are the lengths of the array and the sliding window. There are n integers in the second line.

Output

There are two lines in the output. The first line gives the minimum values in the window at each position, from left to right, respectively. The second line gives the maximum values.

Sample Input

8 3
1 3 -1 -3 5 3 6 7

Sample Output

-1 -3 -3 -3 3 3
3 3 5 5 6 7

题意分析:给定一个数列,从左到右输出每个长度为m的数列段内的最小数和最大数,有一个滑动区间,每次我们找到这个区间的最大值和最小值

解法:从数列的开头,将窗放上去,然后找到这最开始的k个数的最大值,然后窗最后移一个单元,继续找到k个数中的最大值。这种方法每求一个f(i),都要进行k-1次的比较,复杂度为O(Nk)。如果暴力时间复杂度为O(Nm)会超时在暴力枚举的过程中,有一个地方是重复比较了,就是在找当前的f(i)的时候,i的前面其它m-1个数在算f(i-1)的时候我们就比较过了。当你一个个往下找时,每一次都是少一个然后多一个,如果少的不是最大值,然后再问新加进来的,看起来很省时间对吧,那么如果少了的是最大值呢?第二个最大值是什么?那么我们能不能保存上一次的结果呢?当然主要是i的前k-1个数中的最大值了。答案是可以,这就要用到单调队列。

单调队列定义:

 

  • 1、维护区间最值
  • 2、去除冗杂状态 如上题,区间中的两个元素a[i],a[j](假设现在再求最大值)
    若 j>i且a[j]>=a[i] ,a[j]比a[i]还大而且还在后面(核心思想:目前a[j]留在队列肯定比a[i]有用,因为你是往后推!)
  • 3、保持队列单调,最大值是单调递减序列,最小值反之
  • 4、最优选择在队首

 单调队列的维护过程:

  1.维护队首(对于上题就是如果队首已经是当前元素的m个之前,则队首就应该被删了,head++)
  2.在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态,保持单调性)

https://blog.csdn.net/er111er/article/details/78344161

简单举例应用
数列为:6 4 10 10 8 6 4 2 12 14
N=10,K=3;
那么我们构造一个长度为3的单调递减队列:
首先,那6和它的位置0放入队列中,我们用(6,0)表示,每一步插入元素时队列中的元素如下
插入6:(6,0);
插入4:(6,0),(4,1);
插入10:(10,2);
插入第二个10,保留后面那个:(10,3);
插入8:(10,3),(8,4);
插入6:(10,3),(8,4),(6,5);
插入4,之前的10已经超出范围所以排掉:(8,4),(6,5),(4,6);
插入2,同理:(6,5),(4,6),(2,7);
插入12:(12,8);
插入14:(14,9);
那么f(i)就是第i步时队列当中的首元素:6,6,10,10,10,10,8,6,12,14
同理,最小值也可以用单调队列来做。

单调队列的时间复杂度是O(N),因为每个数只会进队和出队一次,所以这个算法的效率还是很高的。
注意:建议直接用数组模拟单调队列,因为系统自带容器不方便而且不易调试,同时,每个数只会进去一次,所以,数组绝对不会爆,空间也是S(N),优于堆或线段树等数据结构。

单调队列有单调递增和单调递减两种,一般来讲,队列的队首是整个队列的最大值或最小值

单调队列可以解决许多问题,而且可以用来优化DP:

  单调队列的实现:(双端队列,一段入,两端输出)

    具体步骤:

      1.   若队列为空,将A[i]从队尾入队

      2.  若队列不为空,将比A[i]大的元素都从队尾弹出,然后把A[i]入队

      3.  若队列不为空且A[i]大于队尾,则直接从队尾把A[i]入队

    实现一般采用双端队列:

复制代码
 1 if(q.empty())
 2   q.push_back(A[i]);
 3 else if(q.back()>A[i]){
 4   while((!q.empty())&&q.back()>A[i]){
 5     q.pop_back();
 6   }
 7   q.push_back(A[i]);
 8 }
 9 else
10   q.push_back(A[i]);
复制代码

单调队列有许多作用:

比如可以求出一个数组内第一个大于等于一个数x的数,也可以通过维护单调性,解决一些区间内最小或最大的问题,总之单调队列的应用在根本上要视题目而定的灵活运用。

单调队列是DP优化的一种,能实现O(VN)的多重背包。单调队列是用一个单调的队列来储存必要的元素,并不储存无用的元素。

单调是一种思想,当我们解决问题的时候发现有许多冗杂无用的状态时,我们可以采用单调思想,用单调队列或类似于单调队列的方法去除冗杂状态,保存我们想要的状态.

Poj 2823代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
    int x,y;
}v[1010000]; //x表示值,y表示位置 可以理解为下标
int a[1010000],n,m,mx[1010000],mn[1010000];
void getmin()
{
    int i,head=1,tail=0;// 默认起始位置为1 因为插入是v[++tail]故初始化为0
    for(i=1;i<m;i++)
    {
        while(head<=tail && v[tail].x>=a[i]) tail--;
        v[++tail].x=a[i],v[tail].y=i;
                // 根据题目 前m-1个先直接进入队列
    }
    for(;i<=n;i++)
    {
        while(head<=tail && v[tail].x>=a[i]) tail--;
        v[++tail].x=a[i],v[tail].y=i;
        while(v[head].y<i-m+1) head++;
        mn[i-m+1]=v[head].x;
               // 道理同上,当然了 要把已经超出范围的从head开始排出
               //  然后每个队首则是目前m个数的最小值
    }
}
void getmax() //最大值同最小值的道理,只不过是维护的是递减队列
{
    int i,head=1,tail=0;
    for(i=1;i<m;i++)
    {
        while(head<=tail && v[tail].x<=a[i]) tail--;
        v[++tail].x=a[i],v[tail].y=i;
    }
    for(;i<=n;i++)
    {
        while(head<=tail && v[tail].x<=a[i]) tail--;
        v[++tail].x=a[i],v[tail].y=i;
        while(v[head].y<i-m+1) head++;
        mx[i-m+1]=v[head].x;
    }
}
int main()
{
    int i,j;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)scanf("%d",&a[i]);
    getmin();
    getmax();
    for(i=1;i<=n-m+1;i++)
    {
        if(i==1)printf("%d",mn[i]);
        else printf(" %d",mn[i]);
    }
    printf("\n");
    for(i=1;i<=n-m+1;i++)
    {
        if(i==1)printf("%d",mx[i]);
        else printf(" %d",mx[i]);
    }
    printf("\n");
    return 0;
}

 单调队列例题:

Leetcode 300:最长上升子序列

https://www.cnblogs.com/yuiffy/p/3885832.html

posted @ 2019-03-02 19:24  zwjjj  阅读(332)  评论(0编辑  收藏  举报