[前后缀分解]:448.最大连续子数组和问题

88 阅读1分钟

刷题列表页

刷题列表页

题目概述

小C拿到了一个数组,他可以进行最多一次操作:将一个元素修改为任意给定的x。小C想知道,经过这次修改后,能够得到的连续子数组的最大和是多少。

题目修正: 必须进行一次操作,即x对答案无贡献也要进行修改


测试样例

样例1:

输入:n = 5 ,x = 10 ,a = [5, -1, -5, -3, 2]
输出:15

样例2:

输入:n = 2 ,x = -3 ,a = [-5, -2]
输出:-2

样例3:

输入:n = 6 ,x = 10 ,a = [4, -2, -11, -1, 4, -1]
输出:15

前置知识

  • 连续子数组: 即数组中连续的一段区间,比如[1,2,3][1,2,3]有6个连续子数组:

    • [1],[2],[3],[1,2],[2,3],[1,2,3][1],[2],[3],[1,2],[2,3],[1,2,3]
    • [1,3][1,3]属于子序列(区间中可以删除一些元素,但不改变原顺序的序列)
  • 前缀和: sum[i]=a[0]+a[1]+a[2]+...+a[i1]sum[i] = a[0] + a[1] + a[2]+...+a[i-1]

  • 后缀和: 反向前缀和: suf[i]=a[n1]+a[n2]+...+a[i+1]suf[i] = a[n-1] + a[n-2] + ... + a[i+1]

解题思路

  • 考虑朴素解法,对于a[i]a[i],我们要考虑计算出它前面最大的贡献和后面的最大贡献,再考虑是否要替换成xx

    • a=[1,2,3,4,5],x=5a = [1,2,3,4,5], x = 5。当i=2i = 2时,计算前面1+2 1 + 2,后面4+54 + 5, 当前a[i]<xa[i]< x替换
    • 这个操作很慢,因为需要遍历当前a[i]a[i]的前面和后面,整体复杂度为O(n2)O(n^2)
  • 考虑优化, 计算前面和后面其实是一个重复的操作

  • 我们用prepre数组表示前缀和,pre[i]pre[i]即在a[i]a[i]之前的所有元素的和(不包含a[i]a[i])

  • sufsuf数组表示后缀和, suf[i+1]suf[i+1]即在a[i]a[i]之后的所有元素的和(不包含a[i]a[i])

  • 特别的,本题存在负数,当前缀和(后缀)中某一段小于0,那么对于答案没有贡献,直接改为0

    • a=[4,9,6,6,4]a = [4,-9,6,-6,4]
    • pre=[0,4,0,6,0,4]pre = [0,4,0,6,0,4]
    • suf=[4,0,6,0,4,0]suf = [4,0,6,0,4,0]
  • 特别的

    • 当数组元素均为负数且最大值小于xx时,答案为xx
    • xx小于数组所有元素,(原题意是不考虑这个情况的),需要提前将最小值换为xx, 替换并不影响后续操作,因为x小于所有数

核心代码

int solution(int n, int x, std::vector<int> a) {
    vector<int>pre(n+1),suf(n+1);
    int idx = 0;
    for(int i=0;i < n;i++)
        if(a[i] < a[idx]) idx = i;
    if(a[idx] > x) a[idx] = x;

    for(int i=1; i <= n; i++)pre[i] = max(pre[i-1] + a[i-1], 0); 
    for(int i=n-1; i >= 0; i--) suf[i] = max(suf[i+1] + a[i], 0);

    int ans = 0;
    int mx = a[0];
    for(int i=0; i < n; i++) {
        ans = max(ans, pre[i] + max(a[i],x) + suf[i+1]);
        mx = max(mx, a[i]);
    }
    if(mx < 0)return max(mx, x);

    return ans;


}