C语言单调队列———最大子序和

关注博主不迷路!
输入一个长度为n的整数序列,从中找出一段不超过m的连续子序列,
使得整个序列的和最大。
输入:第一行两个数n,m
第二行有n个数,要求在n个数中找到最大子序和
输出:最大子序和
样例输入:6 4
1 -3 5 1 -2 3
样例输出: 7

首先我们来分析一下这道题该怎么做!
①我们可以用前缀和来求解,这样会减少他的时间复杂度
②我们需要创建一个前缀和单调递增的队列(Sn)
例如:5 10 14 21这是我们前缀和的序列,如果此时我要入队一个25,显然他仍然遵守单调递增,所以我们可以让他入队,但是为了维持我们的区间为m=4 ,我们需将最小的前缀和,也就是第一个让他出队。 这样就变成了10 14 21 25
如果我们此时是23,显然他的尾部25比23大,如果此时入队则不遵守单调递增规律。所以我们需要将尾部元素踢出队列,然后让23进入队列。那么我们为什么要遵守单调递增的规律呢?
因为只有单调递增才能确保我们区间内的第一个元素是本区间最小的,这样我们在寻找最大值的时候,就能确定让 此值 —减掉 最小值,然后再进行比较。详细的我们来看代码吧

#include <stdio.h>
#include <math.h>
#define N 10000
int arr[N],q[N];	//arr为求和数组,q为队列数组
int max(int x,int y)//求最大值函数
{
    if(x>y) return x;
    if(y>=x) return y;
}
int main()
{
    int n,m,i,k,answer;
    int top=0,tail=0;//定义两个头指针和尾指针(不是真的指针,只为了记住他的位置)
    arr[0] = 0;	//我们先将数组0的位置赋值为0
    answer = arr[1];//先将答案随便赋个值
    scanf("%d%d",&n,&m);
    for(i = 1; i <= n; i ++)
    {
        scanf("%d",&arr[i]);
        arr[i] += arr[i-1];//将数组求前缀和
    }
    q[tail++] = 0;//将队列赋一个元素值为0
    for(k = 1; k <= n; k ++)
    {
        answer = max(answer,arr[k] - arr[q[top]]);//此时进行比较,找出最大值
        //虽然我们入队时保持单调递增的,但是我们的answer已经记录了最大值。
        //例如12 23这个,我们想让20入队,但是要踢出23,显然23是最大的,为什么要踢出23
        //因为保证单调递增嘛!保证了单调递增就保证了第一个元素是最小值,就保证了我们相减的差最大
        //并且我们已经将23赋值给了answer,接下来都是再寻找比23大的元素了
        while(top&&tail&&arr[q[tail - 1]]>=arr[k])  tail--;//核心代码!如果我们的top和tail有值,
        //就证明我们的队列不是空的,然后检验我们的尾部元素是否>我们想要加入的元素,
        //如果不大于它就遵守单调递增,直接入队即可。
        //如果大于我们想要入队的元素,我们需先将尾部元素出队,然后将此元素入队,仍然形成单调递增。
        q[tail++] = k;//将我们入队的元素下标入队。因为我们的队列记录的是下标,而不是值
        if(q[top] == k-m)    top++;//这里我们要确保我们的区间是m,如果区间大于了m,我们就让
        //头指针指向下一个,确保我们一直再区间m中
    }
    printf("%d",answer);
    return 0;
}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值