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

问题描述:

在一组无序的整数(包含负数)中,找出子序列之和在所有子序列中最大的值.若最大子序列之和为负数,则定义最大子序列之和为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){
		//若整数个数为0
		if(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){
		//若整数个数为0
		if(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];
			else
				return 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){
		//若整数个数为0
		if(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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值