算法问题(1)最大子序列问题

给定(有可能是负的)整数,求的最大值。如果所有整数均为负数,则最大子序列和为0。


第一种算法如下,只是穷举式的尝试所有可能。很明显,该算法一定会正确的运行,但运行时间为

/**
 * 最直观简单的实现方式,时间复杂度O(N^3)……
 * @author liufl / 2014年8月5日
 */
public class SumImplSimple implements Sum {

	@Override
	public int maxSubSum(int[] a) {
		int maxSum = 0; // 已知最大和
		for (int i = 0; i < a.length; i++) { // 求和开始位
			for (int j = i; j < a.length; j++) { // 求和结束位
				int thisSum = 0; // 本轮求和缓存
				for (int k = i; k <= j; k++) { // 求和
					thisSum += a[k];
				}
				if (thisSum > maxSum) // 比较本轮和与已知最大和
					maxSum = thisSum;
			}
		}
		return maxSum;
	}

}

第二种方式通过撤除一个for循环,避免了三次的运行时间。但运行时间为 ,也不理想

/**
 * 原简单实现上稍微优化方案,更快的比较出已知最大和结果。时间复杂度O(N^2)……
 * @author liufl / 2014年8月5日
 */
public class SumImplFasterCompare implements Sum {

	@Override
	public int maxSubSum(int[] a) {
		int maxSum = 0; // 已知最大和
		for (int i = 0; i < a.length; i++) { // 求和开始位
			int thisSum = 0; // 本轮最大和
			for (int j = i; j < a.length; j++) { // 求和结束位
				thisSum += a[j]; // 当前和
				if (thisSum > maxSum) { // 比较当前和与已知最大和
					maxSum = thisSum;
				}
			}
		}
		return maxSum;
	}

}

第三种方式使用了递归。该方法使用了“分治”策略。其想法是把问题分成两个大致相等的子问题,然后递归地对它们求解,这是“分”的部分;“治”阶段将两个子问题的解修补到一起并可能再做些少量的附加工作,最后得到整个问题的解。此方式的运行时间为

/**
 * 递归实现。也被称为分治法。时间复杂度O(NlogN)
 * @author liufl / 2014年8月5日
 */
public class SumImplRecursive implements Sum {

	@Override
	public int maxSubSum(int[] a) {
		return this.maxSumRec(a, 0, a.length - 1);
	}

	/**
	 * 递归求子序列的最大子序列和
	 * @param a 原序列
	 * @param left 子序列的左边界
	 * @param right 子序列的右边界
	 * @return
	 */
	private int maxSumRec(int[] a, int left, int right) {
		if (left == right) { // 子序列是一个单值。基本情形
			if (a[left] > 0) {
				return a[left]; // 是正数,返回
			} else {
				return 0; // 不是正数,返回0
			}
		}
		// 中间分成两个子序列,求各子序列的最大子序列和。递归
		int center = (left + right) / 2;
		int maxLeftSum = this.maxSumRec(a, left, center);
		int maxRightSum = this.maxSumRec(a, center + 1, right);
		// 从中部向左加和的最大和
		int maxLeftBorderSum = 0;
		int leftBorderSum = 0;
		for (int i = center; i >= left; i--) {
			leftBorderSum += a[i];
			if (leftBorderSum > maxLeftBorderSum) {
				maxLeftBorderSum = leftBorderSum;
			}
		}
		// 从中部向右加和的最大和
		int maxRightBorderSum = 0;
		int rightBorderSum = 0;
		for (int i = center + 1; i <= right; i++) {
			rightBorderSum += a[i];
			if (rightBorderSum > maxRightBorderSum) {
				maxRightBorderSum = rightBorderSum;
			}
		}
		// maxLeftBorderSum + maxRightBorderSum 中部最大子序列和
		// 取左、右、中部最大子序列和中最大值
		return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);
	}

	/**
	 * 取三值中最大值
	 * @param int1
	 * @param int2
	 * @param int3
	 * @return
	 */
	private int max3(int int1, int int2, int int3) {
		return Math.max(Math.max(int1, int2), int3);
	}

}

最后一种方式运行时间达到了 ,但显然大多数情况下不会自然的想到这种处理。

/**
 * 最佳的方案。正常思维想不到,代码却很好理解。时间复杂度O(N)
 * 但在输入数全为负数时,输出结果是0,而不是负数。
 * @author liufl / 2014年8月5日
 */
public class SumImplBest implements Sum {

	@Override
	public int maxSubSum(int[] a) {
		int maxSum = 0; // 已知最大和
		int thisSum = 0; // 当前有效和
		for (int j = 0; j < a.length; j++) { // 遍历求和
			thisSum += a[j];
			if (thisSum > maxSum) // 比较当前和与已知最大和
				maxSum = thisSum;
			else if (thisSum < 0) // 当前和是负的,会拖累后面的和
				thisSum = 0; // 清零,从下一位重新加和
		}
		return maxSum;
	}

}

代码可在github上pull下来: https://github.com/DowenLiu126/maxSubSum

你可能感兴趣的:(java,算法,最大子序列和)