最大子序列的求解分析(java代码实现)

来源:互联网 发布:linux history 条数 编辑:程序博客网 时间:2024/05/20 12:23

问题描述:

在一组无序的整数(包含负数)中,找出子序列之和在所有子序列中最大的值.若最大子序列之和为负数,则定义最大子序列之和为0.

例子:

一组整数为[1,-3,-2,4,-3,7,-2],则最大子序列为[4,-3,7],故最大和为8.


分析:

1.

最简单的方法,莫过于把所有的子序列都取出来,计算出和,找到最大的子序列;

public class Test {public static void main(String[] args){Test t = new Test();int[] arr = new int[]{1,-3,-2,4,-3,7,-2};System.out.println(t.findMax(arr));}public int findMax(int[] arr){//若整数个数为0if(arr == null || arr.length == 0)return 0;//记录最大值int max = 0;//第一层循环,记录子序列开始位置for(int i = 0; i < arr.length; i++){//第二层循环,记录子序列结束位置for(int j = i; j < arr.length; j++){//记录子序列之和int sum = 0;//第三层循环,计算子序列之和for(int k = i; k <= j; k++){sum += arr[k];}//判断是否是最大值max = max >= sum ? max : sum;}}return max;}}
该方法有三层for循环,时间复杂度为O(N^3).

2.
分析第一种方法,可知在第三层循环中,重复计算了前面的子序列.
对于一组整数为[1,-3,-2,4,-3,7,-2],在i = 0,j = 1的时候计算了序列[1,-3],那么在[1,-3,-2]的时候,可以直接利用前面的结果而无需重复计算.即对于同一个起点i,可以重复利用前面的计算结果.
public class Test {public static void main(String[] args){Test t = new Test();int[] arr = new int[]{1,-3,-2,4,-3,7,-2};System.out.println(t.findMax(arr));}public int findMax(int[] arr){//若整数个数为0if(arr == null || arr.length == 0)return 0;//记录最大值int max = 0;//第一层循环,记录子序列开始位置for(int i = 0; i < arr.length; i++){//记录子序列之和int sum = 0;//第二层循环,记录子序列结束位置for(int j = i; j < arr.length; j++){//重复利用统一起点i的序列之和sum += arr[j];//判断是否是最大值max = max >= sum ? max : sum;}}return max;}}

该方法有二层for循环,时间复杂度为O(N^2).

3.
那我们换一个角度考虑,对于一个整数序列[1,-3,-2,4,-3,7,-2],如果按照分治的思想,将其分为两部分[1,-3,-2,4]和[-3,7,-2],则其最大的子序列无非三种情况,左边序列中的最大值,右边序列中的最大值,或者是以左边序列最右边的整数为头向左的最大子序列之和加上以右边序列最左边的整数为头向右的最大子序列之和的值.
[1,-3,-2,4]最大和为4,[-3,7,-2]最大和为7,([1,-3,-2,4]以4为头的最大和为4)+([-3,7,-2]以-3为头的最大和为4=8),分析正确.
public class Test {public static void main(String[] args) {Test t = new Test();int[] arr = new int[] { 1, -3, -2, 4, -3, 7, -2 };System.out.println(t.findMax(arr, 0, arr.length - 1));}public int findMax(int[] arr, int left, int right) {// 递归结束条件if (left >= right) {if (arr[left] >= 0)return arr[left];elsereturn 0;}// 记录中间数的索引,用来分数组int center = (left + right) / 2;// 左边序列中的最大值int maxLeft = findMax(arr, left, center);// 右边序列中的最大值int maxRight = findMax(arr, center + 1, right);//以左边序列最右边的整数为头向左的最大子序列之和int maxl = 0;int sum = 0;for(int i = center; i>= left; i--){sum += arr[i];maxl = maxl >= sum ? maxl : sum;}//以右边序列最左边的整数为头向右的最大子序列之和int maxr = 0;sum = 0;for(int i = center + 1; i<= right; i++){sum += arr[i];maxr = maxr >= sum ? maxr : sum;}//最大的和int max = (maxLeft >= maxRight) ? maxLeft : maxRight;max = max >= (maxl + maxr) ? max : (maxl + maxr);return max;}}
该方法采用递归的方法,分治处理,时间复杂度为O(NlogN).

4.
继续分析第二种方法,还有没有优化的空间.在第二种方法中,每次起点i变化一次,都要从头进行一次,那么每轮进行的计算并没有充分利用.
设想一下最大子序列i---j,该序列的前缀序列以起点i开头,且前缀序列之和不能为负,因为若前缀为负,则后面的序列之和应该更大.
所以在第二层循环中,若子序列之和为负,则i可以直接移动到j,也就是说,实际上整数序列只需要走一遍.
对于整数[1,-3,-2,4,-3,7,-2],假设i = 0, j = 3,子序列1,-3,-2,4的和为0,故最大的子序列一定不包含该序列,i直接移动到j. 也就是说直接从3开始走,符合上面所说,只走一遍.
public class Test {public static void main(String[] args){Test t = new Test();int[] arr = new int[]{1,-3,-2,4,-3,7,-2};System.out.println(t.findMax(arr));}public int findMax(int[] arr){//若整数个数为0if(arr == null || arr.length == 0)return 0;//记录最大值int max = 0;//记录前缀序列之和int sum = 0;//i记录子序列开始位置for(int i = 0; i < arr.length; i++){sum += arr[i];max = max > sum ? max : sum;if(sum <= 0)sum = 0;}return max;}}
该方法的时间复杂度只有O(N),效率提升明显.


0 0