前端算法第一八六弹-. 删除一次得到子数组最大和

143 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中最大的。

注意,删除一个元素后,子数组 不能为空

示例 1:

输入:arr = [1,-2,0,3]
输出:4
解释:我们可以选出 [1, -2, 0, 3],然后删掉 -2,这样得到 [1, 0, 3],和最大。

示例 2:

输入:arr = [1,-2,-2,3]
输出:3
解释:我们直接选出 [3],这就是最大和。

例 3:

输入:arr = [-1,-1,-1,-1]
输出:-1
解释:最后得到的子数组不能为空,所以我们不能选择 [-1] 并从中删去 -1 来得到 0。
     我们应该直接选择 [-1],或者选择 [-1, -1] 再从中删去一个 -1。

dp方法参考连续子数组的最大和,但现在要区分未删除/删除两个子状态,所以分别用状态ab记录,分别表示:

a:未执行任何删除得到的当前子数组最大和

b:已经执行删除后得到的当前子数组最大和

状态更新: a:不用考虑是否删除,直接更新a = max(a + nums[i], nums[i]) b:考虑执行删除,则当前状态由“之前的最大子数组已经执行了删除,现在只能加上当前的值”和“之前的最大子数组未执行删除,故当前位置可删”更新,即b = max(b + nums[i], a)。

注意事项:

1)a, b中都可以包含“之前子数组值为负,从当前位置重新累积”的状态,不过只需在a中更新即可。 (2)题目要求结果子数组非空,所以将a, b, ans预设为arr[0], 0, arr[0],然后从位置1开始遍历,避免得到删除了nums[0]的空数组即为最大值的情况。 (3)状态更新时因为b依赖a之前的值,所以先更新b,再更新a

var maximumSum = function (arr) {
  if (arr.length === 1) return arr[0]
  let len = arr.length;
  // dpLeft[i]以arr[i-1]为结尾的最大子数组和
  let dpLeft = new Array(len + 1).fill(0)
  // dpRight[i]以arr[i]为开头的最大子数组和
  let dpRight = new Array(len + 1).fill(0)
  // 被删除的数arr[i] 结果就可以表示为 dpLeft[i] + dpRight[i+1]
  // 或者  dpLeft[i] 或者 dpRight[i+1]
  for (let i = 1; i <= len; ++i) {
    dpLeft[i] = Math.max(dpLeft[i - 1], 0) + arr[i - 1];
  }
  for (let i = len - 1; i >= 0; --i) {
    dpRight[i] = Math.max(dpRight[i + 1], 0) + arr[i];
  }
  // i = 0 或者 i = len - 1 的特殊情况 处理一下
  let res = Math.max(dpRight[1], dpLeft[len - 1], dpLeft[len--])
  for (let i = 1; i < len; ++i) {
    if (arr[i] < 0) {
      res = Math.max(res, dpLeft[i], dpRight[i + 1], dpLeft[i] + dpRight[i + 1]);
    }
  }
  return res;
};