单调队列

问题引入-POJ2823

Description

An array of size n ≤ 10 6 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 -13
1 [3  -1  -3] 5  3  6  7 -33
1  3 [-1  -3  5] 3  6  7 -35
1  3  -1 [-3  5  3] 6  7 -35
1  3  -1  -3 [5  3  6] 7 36
1  3  -1  -3  5 [3  6  7]37

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

转载于:https://www.cnblogs.com/zwjjj/p/10462513.html

Python的单调队列是一种用于解决某些特定问题的数据结构。它是队列的一种变体,可以快速查询当前队列中的最大或最小元素。 单调队列通常用于需要维护当前滑动窗口中的最大或最小值的情况。例如,假设我们有一个长度为n的数组arr和一个窗口大小为k的滑动窗口。我们想要找到每个窗口中的最大值。单调队列就可以帮助我们在O(n)的时间复杂度内实现。 实现单调队列需要两个操作:push(x)和pop()。push(x)用于向队列的尾部添加元素x,而pop()用于从队列的头部删除元素。这两个操作具有O(1)的时间复杂度。 当我们向队列中添加一个新元素时,为了维护队列的单调性,我们需要从队列的尾部删除一些元素。具体来说,我们从队列的尾部开始,不断地删除比新元素小的元素,直到队列为空或者新元素大于等于队列尾部元素为止。这样,我们就可以保证队列中的元素是以递减顺序排列的。 当我们需要查询当前队列中的最大或最小值时,只需访问队列头部的元素即可。由于队列中的元素是以递减的顺序排列的,所以头部元素就是最大值(或最小值)。 总的来说,Python的单调队列是一种高效的数据结构,可以用于解决一些特定问题,如滑动窗口中的最大(或最小)值。它具有O(n)的时间复杂度,并且可以通过push(x)和pop()操作来维护队列的单调性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值