单调队列详解

刚学单调队列时,在网上各大博客找文章学,说实话,写得很杂,表示自己懵逼了些许,最后硬是啃出来了,所以我决定要写一篇能让大部分人都看懂的博客来。


说单调队列,那我们就先说说这个单调队列是个什么物种。单调队列从字面上看,无非就是有某种单调性的队列,没错,这就是所谓的单调队列。 单调队列它分两种,一种是单调递增的,另外一种是单调递减的。

在这搬出百度百科的解释:不断地向缓存数组里读入元素,也不时地去掉最老的元素,不定期的询问当前缓存数组里的最小的元素。

用单调队列来解决问题,一般都是需要得到当前的某个范围内的最小值或最大值。


举个例子:有  7 6 8 12 9 10 3 七个数字,现在让你找出范围( i-4,i ) 的最小值。

那我们就可以这样模拟一遍。

先初始化{ 0 } (表示i=0时的值)

i=1 ->{ 0 } (表示i=1,时,在其范围内最小的值为0)-> 7进队 { 7 }

i=2->{ 7 }表示i=2,时,在其范围内最小的值为7)-> 6比7小,7出,6进 { 6 }

i=3-> { 6 } (表示i=3,时,在其范围内最小的值为6)->8比6大,8进  { 6, 8}

i=4->{ 6, 8}(表示i=4,时,在其范围内最小的值为6)-> 12比8大,12进 {6, 8 , 12};

i=5-> {6, 8 , 12}表示i=4,时,在其范围内最小的值为6)-> 9比12小,12out,9比8大,9进 {6,8,  9}

i=6-> {6,8,  9} 但是 单调队列中元素6的下标是2,不在(2, 6],中,故6 out,这就是单调队列的精髓了。故单调队列为

{ 8,9 }(表示i=5,时,在其范围内最小的值为8)->10比9大,10进 最终 单调队列为{ 8,9, 10} ;

i=7->{ 8,9, 10}(表示i=6,时,在其范围内最小的值为8)-> 3比单调队列为{ 8,9, 10} 的任意值都小,故全out,最终集合为 { 3 }


相信大家看完这个例子了解得有些吧,再次重申一遍,单调队列的核心(我认为的哈):得到当前的某个范围内的最小值或最大值。要不是这样的话,那还有必要这么麻烦找吗,直接找前面最小的就好了,可事实不是这样,题目是有限制的,规定在某个范围内找。

那我们就来看到例题,加深理解:


最大子序和

  • Time Limit: 2000/1000 MS (Java/Others)     Memory Limit: 65536/65536 K (Java/Others)
  • Total Submission(s): 9     Accepted Submission(s): 2
Description

一个长度为n的整数序列,从中找出一段不超过m的连续子序列,使得整个序列的和最大。

例如:  1, -3, 5, 1, -2, 3

当m=4时,sum =  5+1-2+3 = 7

当m=2或m=3时,sum = 5+1 = 6

Input

多测试用例,每个测试用例:

第一行是两个正数n, m  ( n, m ≤ 300000 )

第二行是n个整数

Output

每个测试用例输出一行:一个正整数,表示这n个数的最大子序和长度

Sample Input

6 4
1 -3 5 1 -2 3

Sample Output

7


这题可以用dp来解,在这我们就用单调队列来解。

我们先把序列的前i项和加起来并存到一个数组sum[ ]上,那么任意连续的子序列和就为sum [ i ] - sum[ j ] (i>j &&  j>i-m)。

那么我们就可以跟上述例子一样,一直更新就好了,每次找出在(i-m,i)范围内的最小sum[j]

代码一:

#include<cstdio>
#include<algorithm>///单调队列,
#include<cstring>
#include<list>
using namespace std;
typedef long long LL;
const int maxn=300010;
LL sum[maxn];
list <int > que;
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        que.clear(); ///清除 
        sum[0]=0;

        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&sum[i]);
            sum[i]=sum[i-1]+sum[i]; ///求前i项和
        }
        
        ///初始化
        LL maxs=sum[1];

        que.push_front(1);

        for(int i=2;i<=n;i++)
        {

            while(!que.empty()&&i-que.back()>m) ///此步是判断是否在范围(i-m,i)内,不在就pop
                que.pop_back();

            maxs=max(maxs,sum[i]-sum[que.back()]); 
                    ///求最大值,sum[i]-sum[min],表示前i个中找到最小的来减,sum[min]就是单调队列的尾部sum[que.back()]
                    
            while(!que.empty()&&sum[i]<sum[que.front()])  ///更新单调队列,比sum[i]大的值都去掉
                que.pop_front();

            que.push_front(i); ///最后将下标i入队

        }
        printf("%lld\n",maxs);
    }
    return 8;
}

代码二:

#include<cstdio> ///单调递增序列(但保证最可能小)
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const LL maxn=300010;
LL sum[maxn];
LL index1[maxn]; ///存储下标
int main()
{
    LL n,m;
    while(~scanf("%lld%lld",&n,&m))
    {
        sum[0]=0;
        for(int i=1;i<=n;i++){
            scanf("%lld",&sum[i]);
            sum[i]+=sum[i-1]; ///求前i项和
        }
        
        ///初始化
        int left=1,right=1;
        index1[1]=1;
        LL tmax=sum[1];

        for(int i=2;i<=n;i++)
        {
            while(index1[left]<i-m) left++; ///不在范围(i-m,i)内,左移就好了

            tmax=max(tmax,sum[i]-sum[index1[left]]); ///减去范围(i-m,i)内最小的值

            while(right>=left&&sum[i]<sum[index1[right]]) ///排除比值sum[i]大的
                right--;
            right++;
            index1[right]=i; ///将下标添加进去
        }

        printf("%lld\n",tmax);
    }
    return 0;
}


我的标签:做个有情怀的程序员!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值