LintCode第42题-最大子数组 II-使用前缀和优化 + 动态规划法
描述
给定一个整数数组,找出两个 不重叠 子数组使得它们的和最大.
每个子数组的数字在数组中的位置应该是连续的.
返回最大的和
子数组最少包含一个数
样例 1:
输入:
nums = [1, 3, -1, 2, -1, 2]
输出:
7
解释:
最大的子数组为 [1, 3] 和 [2, -1, 2] 或者 [1, 3, -1, 2] 和 [2].
样例 2:
输入:
nums = [5,4]
输出:
9
解释:
最大的子数组为 [5] 和 [4].
挑战
要求时间复杂度为 O(n)
上一篇提到:动态累加和 + 最大子数组和
这一篇使用前缀和优化 + 动态规划
思路:
通过不重叠以及如何计算最大子数组和 我们可以衍生出 如何计算每一个点的最大子数组和
由于不重叠 那么就需要左侧最大子数组和:从左向右,动态记录从左到当前位置的最大子数组和.
右侧最大子数组和:从右向左,动态记录从右到当前位置的最大子数组和.
其中代码的关键点是currentSum - minSum 这个的作用是动态抵消负数
minSum
是“最低水位”,它记录“历史最低前缀和
它的最大值是 0:
因为 0 表示“没有亏损”
如果前缀和中有负数,minSum
会变成负数:
这表示我们有“亏损”
sum - minSum
确保我们可以从“亏损最低点”重新开始计算子数组和
当前点的最大子数组和就是sum−minSum
minSum是"负数之和",表示遍历过程中历史最低前缀和
它记录了 “从起点到当前位置的所有负数累积”:
任何负数都会让 minSum
更低(更负)。
如果没有负数,minSum
会保持为 0(即没有亏损)
currentSum
是“当前所有元素的累加和”:
它从起点到当前位置,累加了所有数(正数和负数)。
如果有正数,它会提升 currentSum
如果有负数,它会降低 currentSum
由此产生一个问题 为什么要动态去掉负数呢
由于题目要求动态计算最大子数组和
子数组和 是数组中一段连续元素的和
负数还会影响后续的累加
假设我们有数组 [3, -5, 2, -1, 4, -3, 5]
:
索引 | 数值 | 当前前缀和(currentSum) | 最小前缀和(minSum) | 当前子数组和(sum - minSum ) | 当前最大子数组和(maxSum) |
---|---|---|---|---|---|
0 | 3 | 3 | 0 | 3 | 3 |
1 | -5 | -2 | 0 | -2 | 3 |
2 | 2 | 0 | -2 | 0 - (-2) = 2 | 3 |
3 | -1 | -1 | -2 | -1 - (-2) = 1 | 3 |
4 | 4 | 3 | -2 | 3 - (-2) = 5 | 5 |
5 | -3 | 0 | -2 | 0 - (-2) = 2 | 5 |
6 | 5 | 5 | -2 | 5 - (-2) = 7 | 7 |
代码如下:
public class Solution {
/**
* @param nums: A list of integers
* @return: An integer denotes the sum of max two non-overlapping subarrays
*/
public int maxTwoSubArrays(List<Integer> nums) {
Integer[] numsArray=new Integer[nums.size()];
for(int i=0;i<nums.size();i++)//初始化
{
numsArray[i]=nums.get(i);
}
Integer maxSum=Integer.MIN_VALUE;
Integer currentSum=0;
Integer minSum=0;
Integer[] presixLeftNum=new Integer[numsArray.length];
Integer[] presixRightNum=new Integer[numsArray.length];
for(int j=0;j<numsArray.length;j++)
{
currentSum=currentSum+numsArray[j];
maxSum = Math.max(maxSum, currentSum - minSum);
minSum=Math.min(currentSum,minSum);
presixLeftNum[j]=maxSum;
}
currentSum = 0;
minSum = 0;
maxSum=Integer.MIN_VALUE;
for(int k=numsArray.length-1;k>=0;k--)
{
currentSum=currentSum+numsArray[k];
maxSum=Math.max(maxSum,currentSum-minSum);
minSum=Math.min(minSum,currentSum);
presixRightNum[k]=maxSum;
}
Integer finalMax = Integer.MIN_VALUE;
for(int i=0;i<numsArray.length-1;i++)
{
finalMax=Math.max(finalMax,presixLeftNum[i]+presixRightNum[i+1]);
}
return finalMax;
}
}