如何求最大子数列之和

本文介绍了暴力求解、动态规划优化的方法来寻找数组的最大子数列和,详细阐述了动态规划的思路,包括如何使用两个变量替代数组以节省空间,并能标识出最大子序列的位置。

1 暴力方法

把所有子列和都求出来

public class Demo07 {

	public static void method(int[] a) {
		int max = Integer.MIN_VALUE;
		int begin = -1;
		int end = -1;
		int len = a.length;
		int sum = 0;
		for (int i = 0; i < len; i++) {// 子数列的开头

			for (int j = i; j < len; j++) {// 子数列的结尾
				sum = 0;
				for (int k = i; k <= j; k++) {// 计算子数列的和
					sum += a[k];
				}
				if (sum > max) {
					max = sum;
					begin = i;
					end = j;
				}
			}
		}
		System.out.println("最大子列和为:" + max + ", 子列开始序号为:" + begin + ", 子列结束序号为:" + end);
	}

	public static void main(String[] args) {
		int[] a = { 1, -5, -9, 7, -5, 4, 3, -8, 5 };
		method(a);
	}

}

运行结果:

最大子列和为:9, 子列开始序号为:3, 子列结束序号为:6

2 第一种方法的改进:重复利用已经计算的子数组和

public static void method(int[] a) {
		int max = Integer.MIN_VALUE;
		int begin = -1;
		int end = -1;
		int len = a.length;
		int sum = 0;
		for (int i = 0; i < len; i++) {// 子数列的开头
			sum = 0;
			for (int j = i; j < len; j++) {// 子数列的结尾
				sum += a[j];// 从i加到j,下一次循环的时候,利用了上一次循环的和
				if (sum > max) {
					max = sum;
					begin = i;
					end = j;
				}
			}
		}
		System.out.println("最大子列和为:" + max + ", 子列开始序号为:" + begin + ", 子列结束序号为:" + end);
	}

3 动态规划方法【创建了另外两个数列】

转化为动态规划方法解题
具备动态规划的两个特征:具有规模;有递推公式。
具体说明:
对于序号为i的元素来说,前i+1个元素的子序列最大和设为all[i]
设包含i在内的子序列最大和为end[i]
a[i]怎么求解呢?是下面两种情况中大的那个:

  1. 包含i号元素,即为end[i]。怎么求解呢分为两种情况,选择大的那个:不包含i-1,则为a[i];包含i-1,则为end[i-1] + arr[i]
  2. 不包含i号元素,就为all[i-1]

代码展示如下:

	public static void method(int[] a) {
		int n = a.length;
		int[] end = new int[n];
		int[] all = new int[n];
		end[0] = all[0] = a[0];
		for (int i = 1; i < n; i++) {
			end[i] = a[i] > (end[i - 1] + a[i]) ? a[i] : (end[i - 1] + a[i]);
			all[i] = all[i - 1] > end[i] ? all[i - 1] : end[i];
		}
		System.out.println("最大子列和为:" + all[n - 1]);// 结果应该是9
	}

	public static void main(String[] args) {
		int[] a = { 1, -5, -9, 7, -5, 4, 3, -8, 5 };
		method(a);
	}

4 优化的动态规划方法【用两个变量来储存end和all】

我们发现就用了i-1的数据,那么我们没有必要新建两个数组来储存end和all的值,用两个int变量来储存就行了。

	public static void method(int[] a) {
		int n = a.length;
		int end = a[0];
		int all = a[0];
		for (int i = 1; i < n; i++) {
			end = a[i] > (end + a[i]) ? a[i] : (end + a[i]);
			all = all > end ? all : end;
		}
		System.out.println("最大子列和为:" + all);// 结果应该是9
	}

5 最后一种方法,还能标出子序列的位置【不储存all的值,如果有大的end就更新all】

方法3和方法4中,end[i]=man{a[i], end[i-1]+a[i]}
可以看出,如果end[i-1]的值如果是正数,end[i] = end[i-1] + a[i];否则就为a[i]
对于下面的代码来说:
如果subSum为负,我们,更新subStart为i,subSum为a[i]
否则,我们更新subSum的值为subSum + a[i]
如果subSum大于max则更新max的值,更新开始,结束的序号值
代码如下:

	public static int begin = 0;
	public static int end = 0;

	public static int method(int[] a) {
		int max = Integer.MIN_VALUE;
		int subSum = a[0];
		int subStart = 0;
		for (int i = 1; i < a.length; i++) {
			if (subSum < 0) {
				subStart = i;
				subSum = a[i];
			} else {
				subSum += a[i];
			}
			if (subSum > max) {
				max = subSum;
				begin = subStart;
				end = i;
			}
		}

		return max;
	}

	public static void main(String[] args) {
		int[] a = { 1, -5, -9, 7, -5, 4, 3, -8, 5 };
		System.out.println(method(a));// 9
		System.out.println(begin);// 3
		System.out.println(end);// 6
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值