最大子序和问题
动态规划、分治法
题目
根据题目可知,9个数字共有9+8+7+……+1=45种解法,如果遍历求最大值,时间复杂度为O^2,这个速度太慢。
解法一:动态规划
我们把上面的45种组合进行分类,分解成数量较少的几个子问题:
1.以第一个数字结尾的连续序列:[-2],最大值-2。
2.以第二个数字结尾的连续序列:[-2,1],[1],最大值1。
3.以第三个数字结尾的连续序列……
如果能得到这几个序列的最大值,那么比较这9个子组合的最大值就可以得到整体的最大值了。比较组合二和组合三,可以得到规律即:第n个组合只是在第(n-1)个组合的基础上每一个数组增加第n个数字,然后再增加数组[n]。将第n个组合分成两种情况:
1.继承n-1组合得到的;
2.新生成的,即[n];
从加和的角度来说,假设第n个数字是负数,那么n无法给前面n-1个组合带来正增长,那么第n-1个组合就会抛弃第n个数字,自己就是最大的。
伪代码:if max(n-1)>0,max(n)=max(n-1)+nums(n);
else max(n)=nums(n) ;
也就是说,我们只需要一个变量来保存前面子组合的最大值。另一个变量保存全局最大值。
代码如下:
public int maxSubArray(int[] nums) {
if (nums == null) {
return 0;
}
int max = nums[0]; // 全局最大值
int subMax = nums[0]; // 前一个子组合的最大值
for (int i = 1; i < nums.length; i++) {
if (subMax > 0) {
// 前一个子组合最大值大于0,正增益
subMax = subMax + nums[i];
} else {
// 前一个子组合最大值小于0,抛弃前面的结果
subMax = nums[i];
}
// 计算全局最大值
max = Math.max(max, subMax);
}
return max;
}
解法二:分治法
分治法就是将整个数组划分成几个小组,每个小组再划分成几个更小的小组,一直到只剩下一个小组为止。每个小组会计算出最优值,汇报给上一级的小组,上级根据小组的汇报比较爱得出最大值。
这个问题的关键在于如何划分使得每个小组之间没有重复的组合,这个数组[-2,1,-3,4,-1,2,1,-5,4]一共有9个元素,根据center=(start+end)/2这个原则,得到中间元素的索引为4.也就是-1,拆分成三个组合,
[-2,1,-3,4,-1]一组;
[2,1,-5,4]一组;
包含左边序列最右边一组和右边序列最左边元素的序列;
代码如下:
public int maxSubArray(int[] nums) {
return maxSubArrayDivideWithBorder(nums, 0, nums.length-1);
}
private int maxSubArrayDivideWithBorder(int[] nums, int start, int end) {
if (start == end) {
// 只有一个元素,也就是递归的结束情况
return nums[start];
}
int center = (start + end) / 2;
int leftMax = maxSubArrayDivideWithBorder(nums, start, center); // 计算左侧子序列最大值
int rightMax = maxSubArrayDivideWithBorder(nums, center + 1, end); // 计算右侧子序列最大值
// 计算包含左侧子序列最后一个元素的子序列最大值
int leftCrossMax = Integer.MIN_VALUE; // 初始化一个值
int leftCrossSum = 0;
for (int i = center ; i >= start ; i --) {
leftCrossSum += nums[i];
leftCrossMax = Math.max(leftCrossSum, leftCrossMax);
}
int rightCrossMax = nums[center+1];
int rightCrossSum = 0;
for (int i = center + 1; i <= end ; i ++) {
rightCrossSum += nums[i];
rightCrossMax = Math.max(rightCrossSum, rightCrossMax);
}
int crossMax = leftCrossMax + rightCrossMax;
return Math.max(crossMax, Math.max(leftMax, rightMax));
}