最大子列和问题

给定K个整数组成的序列{ N​1​​, N​2​​, …, N​K​​ },“连续子列”被定义为{ N​i​​, N​i+1​​, …, N​j​​ },其中 1≤i≤j≤K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。

本题旨在测试各种不同的算法在各种数据情况下的表现。各组测试数据特点如下:

数据1:与样例等价,测试基本正确性;
数据2:102个随机整数;
数据3:103个随机整数;
数据4:104个随机整数;
数据5:105个随机整数;

输入格式:

输入第1行给出正整数K (≤100000);第2行给出K个整数,其间以空格分隔。

输出格式:

在一行中输出最大子列和。如果序列中所有整数皆为负数,则输出0。

输入样例:

6
-2 11 -4 13 -5 -2

输出样例:

20

算法一:
用三重循环,外重循环遍历子列左端位置,中间层循环遍历子列右端位置,内层循环对i到j的子列进行求和。

/**
	 * 算法一,三重循环
	 * @param a 数组a
	 * @param k 数组a的长度
	 * @return MaxSum 最大子列和
	 */
	private static int MaxSubseqSum1(int[] a, int k) {
		int ThisSum ,MaxSum = 0;
		for (int i = 0; i <k; i++) {		//i是子列左端位置
			for (int j = i; j < k; j++) {	//j是子列右端位置
				ThisSum = 0;						//ThisSum是从A[i]到A[j]的子列和,将ThisSum置0
				for (int j2 = i; j2 < j; j2++) {
					ThisSum += a[j2];
				}
				if (ThisSum > MaxSum) {
					MaxSum = ThisSum;		//如果刚得到的这个子列和更大,则更新结果
				}
			}
		}
		return MaxSum;
	}

很明显这个算法的时间复杂度为在这里插入图片描述

代码提交之后,5个测试用例仅能通过两个,看来算法还需要在完善,如图:

在这里插入图片描述算法二:
用两重循环,外重循环遍历子列左端位置,内层循环遍历子列右端位置,对于相同的i,不同的j,只要在j-1次循环的基础上累加1项即可,减少了一层循环。

/**
	 * 算法二,用两重循环
	 * @param a 数组a
	 * @param k 数组a的长度
	 * @return MaxSum 最大子列和
	 */
	private static int MaxSubseqSum2(int[] a, int k) {
		int ThisSum ,MaxSum = 0;
		for (int i = 0; i < k; i++) {		//子列左端位置
			ThisSum = 0;							//ThisSum是从A[i]到A[j]的子列和 
			for (int j = i; j < k; j++) {	//子列右端位置
				ThisSum += a[j];
				if (ThisSum > MaxSum ) {
					MaxSum = ThisSum;				//如果刚得到的这个子列和更大,则更新结果
				}
			}
		}
		return MaxSum;
	}

算法二减少了一层循环,很明显这个算法的时间复杂度为在这里插入图片描述
代码提交之后,5个测试用例仅能通过4个,当数据规模达到105个随机整数时,内存超限了,当如图:
在这里插入图片描述
算法三:
用分治法来处理,每次将序列从中间划分开,分别处理左子列、右子列、以及跨分界线的最大子列和,返回三者中的最大值。
举个例子:
在这里插入图片描述

	/**
	 * 算法三,保持与前2种算法相同的函数接口
	 * @param a
	 * @param k
	 * @return
	 */
	private static int MaxSubseqSum3(int[] a, int k) {
		return DivideAndConquer(a,0,k-1);
	}

	/**
	 * 返回3个整数中的最大值
	 * @param A
	 * @param B
	 * @param C
	 * @return
	 */
	private static int max3(int A,int B,int C) {
		return A>B?A>C?A:C:B>C?B:C;
	}
	
	/**
	 * 分治法求a[left]到a[right]的最大子列和
	 * @param a 数组a
	 * @param left 
	 * @param rignt 
	 * @return
	 */
	private static int DivideAndConquer(int[] a, int left, int right) {
		int MaxLeftSum,MaxRightSum;					//存放左右子问题的解
		int MaxLeftBorderSum,MaxRightBorderSum;		//存放跨分界线的结果
		int LeftBorderSum,RightBoederSum;			
		int center;
		
		if (left == right) {						//递归的终止条件,子列只有1个数字
			if (a[left] > 0) {
				return a[left];
			}else {
				return 0;
			}
		}
		
		//下面是"分"的过程
		center = (left + right)/2;					//找到中分点
		//递归求得两边子列的最大和
		MaxLeftSum  = DivideAndConquer(a, left, center);
		MaxRightSum = DivideAndConquer(a, center+1, right);
		
		//下面求跨分界线的最大子列和
		//从中间往左边扫描
		MaxLeftBorderSum = 0;
		LeftBorderSum = 0;
		for (int i = center; i >= left; i--) {
			LeftBorderSum += a[i];
			if (LeftBorderSum > MaxLeftBorderSum) {
				MaxLeftBorderSum = LeftBorderSum;
			}
		}
		
		//从中间往右边扫描
		MaxRightBorderSum = 0;
		RightBoederSum = 0;
		for (int i = center+1; i <= right; i++) {
			RightBoederSum  += a[i];
			if (RightBoederSum > MaxRightBorderSum) {
				MaxRightBorderSum = RightBoederSum;
			}
		}
				
		//下面返回"治"的结果
		return max3(MaxLeftSum, MaxRightSum, MaxLeftBorderSum+MaxRightBorderSum);
	}

由于分治法将问题划分为两个子问题,每个子问题的规模都为原来的一半,最后还要加上处理跨分界线的最大子列和的规模(即将整个序列扫描一遍,为N),所以算法的时间复杂度为O(N logN)。
在这里插入图片描述

算法四:
在线处理。“在线”的意思是指每输入一个数据就进行即时处理,在任何一个地方中止输入,算法都能正确给出当前的解。

	/**
	 * 算法四,在线处理
	 * @param a 数组a
	 * @param k 数组a的元素个数
	 * @return MaxSum 最大子列和
	 */
	private static int MaxSubseqSum4(int[] a, int k) {
		int ThisSum,MaxSum;
		ThisSum = MaxSum = 0;
		for (int i = 0; i < k; i++) {
			ThisSum  += a[i];				//向右累加
			if (ThisSum > MaxSum) {
				MaxSum = ThisSum;			//发现更大和则更新当前结果
			}else if (ThisSum < 0) {
				ThisSum = 0;				//如果当前子列和为负,则不可能使后面的部分和增大,抛弃之
			}
		}
		return MaxSum;
	}

算法四只有一层for循环,时间复杂度是O(N)。也是这个问题所能达到的最佳时间复杂度了,毕竟,要求出序列的最大子列和至少的把整个扫面一遍吧。这种方法可以算是近乎完美的一种求解最大子列和的算法。
这种算法效率之所以如此高,是因为任何负的子序列都不可能是最有子序列的前缀,所以我们可以知道,只要是首位元素为负数的肯定不是最大子列的组成部分,我们可以将其抛弃。即如果某个子列的首位为负数,那么它一定要借助后面的非负数改进。
而且这种算法还有一种优点就是,它只对数据扫描一次,在任何时刻,算法都可以对它已经读入的数据给出正确的答案。

另外,给出调用以上算法的main()方法:

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int K = scanner.nextInt();
		int[] a = new int[K];
		for (int i = 0; i < K; i++) {
			a[i] = scanner.nextInt();
		}
		
		//int ans = MaxSubseqSum1(a,K);
		//int ans = MaxSubseqSum2(a,K);
		//int ans = MaxSubseqSum3(a,K);
		int ans = MaxSubseqSum4(a,K);
		System.out.println(ans);
	}
  • 15
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值