求序列的最大子序列和的问题

来源:互联网 发布:java hmacmd5 编辑:程序博客网 时间:2024/05/17 07:25

给定任意序列: A_1,A_2,A_3,...,A_N...

求解 : A_i...A_j的和的最大值(1 <= i  <= j  <= N)

这里分为递归和非递归两种方法实现。其中非递归实现的方法更优(复杂度O(N) )。

对于这个问题,一开始很容易想到用两个循环嵌套,逐一去遍历以序列每一个元素为起点的子序列的和,从而去求出最大子序列和的问题

public int maxSubSum1(int[] arr) {int maxSum = 0;for (int i = 0; i < arr.length; i++) { //外层主要是为了限定每次子序列的起点(j = i)int currentSum = 0;for (int j = i; j < arr.length; j++) { //内层逐一去遍历以i为起点的子序列的最大和currentSum += arr[j];if(currentSum > maxSum)maxSum = currentSum;}}return maxSum;}

不过这种方法复杂度为O(N^2),效率不好,我们可以优化,也就是要说的递归实现。该方法采用了常见的“分治(Divide-and-Conquer)策略”,也就是把问题分解成两个子问题,再递归的对子问题求解,直到基准情况(我们设定的有解的部分),再回溯,把各个子问题的解合并为整个问题的解。

在这个问题中,最大子序列只可能出现在三处地方:以第一个元素为起点的序列的左半部;以最后一个元素为中点的序列的右半部;跨过(包含)中间元素,分别向左右扩散的序列。

前面两种情况都可以用递归求解;第三种就通过分别以中间元素为起点,求出以中间元素为终点的前半部分的最大和,以及以中间元素为起点的后半部分半部分的最大和,两者的和就是了。

得到这三个最大和,再求它们三者中最大就是子序列最大和了。

//递归实现  (复杂度: O(N*logN))public int maxSumRec(int[] arr, int left, int right) {//基准情况if(left == right) { //就是只有一个元素了,若是它大于0 ,就是最大元素,返回它if(arr[left] > 0) {//System.out.println("基准===" + left);return arr[left];}else {//System.out.println("基准" + 0);return 0;}}//分成左右两部分子序列求解int center = (left + right)/2;//System.out.println("左:来了" + center);int maxLeftSum = maxSumRec(arr, left, center);//System.out.println("左最大" + maxLeftSum);//System.out.println("右:来了" + center);int maxRightSum = maxSumRec(arr, center+1, right);//System.out.println("右最大" + maxRightSum);//从中间向两边分别求最大和int maxLeftBorderSum = 0, leftBorderSum = 0;for(int i = center; i >= left; i--) {//System.out.println("左半:" + center);leftBorderSum += arr[i];if(leftBorderSum > maxLeftBorderSum)maxLeftBorderSum = leftBorderSum;}int maxRightBorderSum = 0, rightBorderSum = 0;for(int j = center+1; j <= right; j++) {//System.out.println("右半:" + (center+1));rightBorderSum += arr[j];if(rightBorderSum > maxRightBorderSum)maxRightBorderSum = rightBorderSum;}return max(maxLeftSum, maxRightSum, maxLeftBorderSum+maxRightBorderSum);}private int max(int maxLeftSum, int maxRightSum, int maxMidSum) {int max = maxMidSum;if(maxLeftSum > max)max = maxLeftSum;else if(maxRightSum > max)max = maxRightSum;return max;        }
最后就是非递归实现了。前面求子序列最大和过程中会有多余的求和运算,也就是某个数在前一个子序列求和过程中已经加过了,但是下一个或者下下个求个时又要算一遍,就会使得效率不好了。这时的非递归实现就会尽量避免这种问题,若是序列某一元素(下标为j)使得该元素(j)前面的子序列和小于零,那么该和为负的子序列就不可能是最优子序列的前缀,下一次求和就可以跳过j,直接从(j+1)开始重新求和。关键就在于理解这步跳过i的过程。下面讲讲我的理解,其实蛮简单的。。。

以i为起点的子序列,一直求和到(j-1)子序列和都为正的,但是当加到下一个元素j时,序列和变为负了,也就是 :(a[i] + a[i+1] + .. + a[j-1]) < |a[j]| (a[j] < 0)。

假设p为 (i+1)~ j 中任一下标,则 (a[i] + a[i+1] + .. + a[p-1])必为正,而 (a[p] + a[p+1] + .. + a[j])必为负,所以 (i+1)~ j 中任一子序列也不可能是最优子序列的前缀,

那就可以直接跳过j,从(J+1)开始求和了。

//非递归实现  (复杂度: O(N))public int maxSubSum2(int[] arr) {int maxSum = 0, currentSum = 0;for (int i = 0; i < arr.length; i++) {currentSum += arr[i];if(currentSum > maxSum)maxSum = currentSum;else if(currentSum < 0) //arr[i]使得当前子序列和小于零,当前序列肯定不是最优子序列的前缀,可以直接跳过,即 currentSum重新开始计数currentSum = 0;}return maxSum;}