单调队列求滑动窗口最值:最大子序和

题目描述

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

注意: 子序列的长度至少是1。

输入格式

第一行输入两个整数n,m。

第二行输入n个数,代表长度为n的整数序列。

同一行数之间用空格隔开。

输出格式

输出一个整数,代表该序列的最大子序和。

数据范围

1 ≤ n , m ≤ 300000 1≤n,m≤300000 1n,m300000

输入样例

6 4
1 -3 5 1 -2 3

出样例

7

算法思想(前缀和+单调队列求滑动窗口最值)

根据题目描述,找出一段长度不超过m的连续子序列,使得子序列中所有数的和最大,属于最优化问题。

最优化问题一般可以描述为在一个有限集合中求最值,或者是方案数。比如背包问题,在一个有限容量( m m m)的背包中,对于 n n n个确定价值( w w w)和体积( v v v)的商品,对于商品的不同选择方案一定是一个有限集合。那么在这个有限集合中找到一个总价值最大的选择方案,就是从集合角度分析最优化问题。

对于本题来说,可以将长度为 n n n的整数序列中所有长度不超过 m m m的连续子序列看作一个全集,要求的是在这个全集中找到一个连续子序列和最大的序列。在求解之前需要将集合分类,分类的依据就是找到一个不同点,本题中可以根据序列中最后一个数在整数序列中的位置将集合划分成若干个子集,如下图所示:
在这里插入图片描述
那么,求出其中每一类的最大值,然后取其中最大的即为最终结果。

不妨以a[k]结尾的子序列为例求该类的最大值,那么长度不超过m的子序列如下图所示:
在这里插入图片描述
m个连续子序列的和可以利用前缀和求得,分别为:

  • s[k] - s[k - 1]
  • s[k] - s[k - 2]
  • s[k] - s[k - 3]
  • s[k] - s[k - m]

所有长度为j 1 ≤ j ≤ m 1\le j\le m 1jm)的连续子序列的和,都可以表示为s[k] - s[k - j]。其中s[k]是固定的,那么求最大值,只需要求k之前大小为m的区间中一个最小的s[i]即可。这样本题可以转换为长度为m的滑动窗口求最小前缀和问题,可以使用单调上升队列优化。

时间复杂度

单调队列求最值,时间复杂度为 O ( n ) O(n) O(n)

代码实现

#include <iostream>
#include <cstdio>
using namespace std;

const int N = 300010, INF = 0x3f3f3f3f;
//q[]单调队列,存储的是数字在序列中的位置
int s[N], q[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);    
    for(int i = 1; i <= n; i ++)
    {
        scanf("%d", &s[i]);
        s[i] += s[i - 1]; //计算前缀和
    }    
    int hh = 0, tt = 0; //单调队列队头和队尾指针
    q[tt] = 0; //因为要求前缀和的差,所以将0入队
    int ans = -INF;
    //滑动窗口求最值
    for(int i = 1; i <= n; i ++)
    {
        //检查队头是否已滑出窗口
        //此时i还没有入队,所以窗口长度为m - 1
        if(hh <= tt && q[hh] + m < i) hh ++; 
        
        //打擂台求连续子序列和的最大值
        ans = max(ans, s[i] - s[q[hh]]);
        
        //单调队列优化,将队尾所有前缀和大于等于s[i]的元素出队
        while(hh <= tt && s[q[tt]] >= s[i]) tt --;
        
        //当前位置入队
        q[++ tt] = i;
    }
    
    printf("%d\n", ans);    
    return 0;
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值