最大字段和问题
最大子段和 -- 分治,dp
1.问题描述
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
2.分析问题
2.1 方案1
此题很显然可以用多层for循环解决, 时间复杂度为O(n^2) .
2.2 方案2, 图像压缩问题的变形
-
采用dp的思路, 我们把这个数组分成不同的段, **dp[i] 表示以nums[i]结尾的那个段的最大字段和, 所以就存在nums[i]这个元素是加入前一段中, 还是自成一段这就是需要思考的问题, 如果dp[i-1] < 0, 如果nums[i]加入前一段中, 最大字段和为dp[i-1] + nums[i], 这个值必然小于nums[i], 因为dp[i-1]是负数, 负数加上一个数,必然比这个数小, 就像是一个数减了某个数, 肯定变小. 其他情况下只用求dp[i-1] + nums[i] 和 nums[i] 的最大值即可 **
-
通过上面的分析, 很容易写出状态转移方程
// dp初始化
dp[0] = nums[0]
if(dp[i-1] < 0) dp[i] = nums[i]; // 自成一段
if(dp[i-1] >= 0) dp[i] = Max(dp[i-1] + nums[i], nums[i])
- 简化转移方程, dp[i-1]<0时, nums[i] + dp[i-1] 必然是小于nums[i]的, 其实也是在求Max(dp[i-1] + nums[i], nums[i]),所以这个题只用一个方程就搞定. 简化了if else的判断, 对程序性能提升也是有帮助的
dp[0] = nums[0]
dp[i] = Max(dp[i-1] + nums[i], nums[i])
-
这个题很有个很坑的地方就是, dp中最后一个元素并不是最终要求的结果, 这个我们平时做的题有很大的出入, dp[i]的含义是以nums[i]结尾的那个段的最大字段和, 那么dp中最后一个元素表示的是以nums中最后一个元素结尾的那个段的最大字段和, 最大的字段和不一定以nums中最后一个元素结尾,所以要最终要求的目标是dp数组中的最大值
public int maxSubArray(int[] nums) { if(nums == null || nums.length <= 0) throw new IllegalArgumentException(); int[] dp = new int[nums.length]; dp[0] = nums[0]; for(int i = 1; i < nums.length; ++i){ dp[i] = Math.max(nums[i], nums[i] + dp[i-1]); } // return dp[nums.length-1]; 神坑 int maxValue = Integer.MIN_VALUE; for(int j = 0; j < dp.length; ++j){ if(dp[j] > maxValue)maxValue = dp[j]; } return maxValue;
3. 方案3
-
采用分治的思路, 我们把数组从中间分开, 最大子序列的位置就存在以下三种情况
- 最大子序列在左半边, 采用递归解决
- 最大子序列在右半边, 采用递归解决
- 最大子序列横跨左右半边, 左边的最大值加上右边的最大值
-
时间复杂度分析
T(n) = 2 F(n/2) + n
时间复杂度O(nlgn)
public int maxSubArray(int[] nums) {
if(nums == null || nums.length <= 0) throw new IllegalArgumentException();
return helper(nums, 0, nums.length-1);
}
private int helper(int [] nums, int start, int end){
if(nums == null || nums.length <= 0) throw new IllegalArgumentException();
if(start == end)return nums[start];
int middle = start + (end - start) / 2;
int leftSums = helper(nums,start, middle);
int rightSums = helper(nums,middle+1, end);
// 横跨左右两边
int leftRightSums;
// 左边的最大值
int lsums = Integer.MIN_VALUE, temp = 0;
for(int i = middle; i >= start; i--){
temp += nums[i];
if(temp > lsums)lsums = temp;
}
// 右边的最大值
int rsums = Integer.MIN_VALUE;
temp = 0;
for(int j = middle+1; j <= end; j++){
temp += nums[j];
if(temp > rsums) rsums = temp;
}
leftRightSums = rsums + lsums;
return Math.max(Math.max(leftSums, rightSums), leftRightSums);
}