给定(有可能是负的)整数,求的最大值。如果所有整数均为负数,则最大子序列和为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