最大子数组和问题
问题定义:
求数组A中一个连续的和最大的子数组。比如数组[4,5,-6,7,-3,1]的最大子数组是[4,5,-6,7],和为10。
解法一:分治法
可以把问题转化成求两个子数组的最大子数组问题。令mid为数组A[low, high]的中间位置,则A[low, high]的最大子数组所处的位置存在如下三种情况:
1、完全位于A[low, mid]之中
2、完全位于A[mid+1, high]之中
3、横跨中点mid
对于1、2两种情况相当于将问题的规模缩小为原来的一半,因此只需要递归求解即可。第3种情况需要单独考虑。
首先可知横跨中点mid的子数组中一定包含元素A[mid],则我们可以将第3种情况一分为二,任何横跨中点的子数组都由两个子数组A[i,..,mid]和A[mid+1,..,j]组成,所以我们可以以中点mid为界,向两边寻找两个最大子数组A[i,..,mid]和A[mid+1,..,j],然后再将其合并即可。寻找横跨中点的最大子数组代码如下:
1 public int[] findCrossMaxSubArray(int[] A, int low, int mid, int high) { 2 int curSum, leftSum, rightSum; 3 curSum = leftSum = rightSum = 0; 4 int i,j, begin = 0, end = 0; 5 for (i = mid; i >= low; i--) { 6 curSum += A[i]; 7 if (curSum > leftSum) { 8 leftSum = curSum; 9 begin = i; 10 } 11 } 12 for (j = mid + 1, curSum = 0; j <= high; j++) { 13 curSum += A[j]; 14 if (curSum > rightSum) { 15 rightSum = curSum; 16 end = j; 17 } 18 } 19 return new int[]{begin, end, leftSum+rightSum}; 20 }
下标i标识的是子数组的起始位置,j标识的是终止位置,leftSum和rightSum分别存储两个子数组中的最大的和。
解决了以上问题,那么求数组A[low, high]的最大子数组就迎刃而解了,代码如下:
1 public static int[] findMaxSubArray(int[] A, int low, int high) { 2 // base case:只有一个元素 3 if (low == high) { 4 return new int[] {low, high, A[low]}; 5 } else { 6 int mid = (low + high) >>> 1; 7 // 分别求出A[low, mid], A[mid+1, high], 以及横跨中点 8 // 的三个最大子数组。 9 int[] left = findMaxSubArray(A, low, mid); 10 int[] right = findMaxSubArray(A, mid+1, high); 11 int[] cross = findCrossMaxSubArray(A, low, mid, high); 12 // 返回三者中和最大的一个 13 int leftSum = left[2], rightSum = right[2], crossSum = cross[2]; 14 if (leftSum >= rightSum && leftSum >= crossSum) { 15 return left; 16 } else if (rightSum >= leftSum && rightSum >= crossSum) { 17 return right; 18 } else { 19 return cross; 20 } 21 22 } 23 }