持续创作,加速成长!这是我参与「掘金日新计划 · 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方法参考连续子数组的最大和,但现在要区分未删除/删除两个子状态,所以分别用状态a和b记录,分别表示:
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;
};