求最大子段和问题
求最大子段和的问题是比较经典的算法,可以由简单的算法到复杂的算法逐步递进。
问题描述:
给定序列a [1],a [2],a [3] …… a [n],您的工作是计算子序列的最大和。
例如,给定(6,-1,5, 4,-7),此序列中的最大和为6 +(-1)+ 5 + 4 = 14。
给定(0,6,-1,1,-6,7,-5),最大和为7。
算法一:穷举法(枚举法)
把所有的情况都算出来,取出最大值,这种算法的时间复杂度为O(n^3),i从数组头部到尾部,j从i到数组尾部,k从i到j。
public int MaxSum(int[] array) {
int currentSum = 0;
int sum = 0;
for (int i = 0; i < array.length; i++) {
for (int j = i; j < array.length; j++) {
currentSum = 0;
for (int k = i; k <= j; k++) {
currentSum = currentSum + array[k];
}
if (currentSum > sum) {
sum = currentSum;
}
}
}
return sum;
}
如果看不懂上面的算法,可以在纸上写出来,用了三层循环遍历计算所有的结果,在上述代码中其实有好多的计算是重复的,例如:当i=0,j=3时要计算a[0]+a[1]+a[2]+a[3],当j=4时,要计算a[0]+a[1]+a[2]+a[3]+a[4],显然这个计算有一部分是多余的。第一层循环从0到array的长度-1,第二、三层循环主要负责计算子段的和,把每次计算的子段和和存到currentSum变量中,与sum进行比较(sum存储的是最终的最大子段和),如果大则进行交换。通过分析上面的代码有大量的重复计算,效率不高。
算法二:依然是枚举法
分析算法一发现有大量的重复计算,避免这些重复计算可以提高很多的效率,我们发现算法一中的一个循环可以去掉。
public int MaxSum1(int[] array) {
int currentSum = 0;
int sum = 0;
for (int i = 0; i < array.length; i++) {
currentSum = 0;
for (int j = i; j < array.length; j++) {
currentSum += array[j];
if (currentSum > sum) {
sum = currentSum;
}
}
}
return sum;
}
虽然算法二较算法一减少了一层循环,时间复杂度可以减少到O(n^2)。不需要k,在找到j的时候就将子段和记录下来。但是依然存在重复的计算,还不是最优的。
算法三:动态规划
目标减少重复计算的次数,尽可能避免重复的计算过程,依然是减少循环
public int MaxSum2(int[] array) {
int currentSum = 0;
int sum = 0;
for (int i = 0; i < array.length; i++) {
currentSum += array[i];
if (currentSum > sum) {
sum = currentSum;
}
if (currentSum < 0) {
currentSum = 0;
}
}
return sum;
}
上面的算法过程是循环遍历整个数组,接着计算每个子段的和,判断这个子段和,第一次循环如果大于0则赋值给sum,小于0不计算当前元素,接着第二次循环加上上次循环中计算出的子段和……
下面是动态规划算法另一种实现形式:
public int MaxSum3(int[] array) {
int num = array.length;
int sum = 0;
int y = 0;
for (int i = 0; i < num; i++) {
if (y > 0) {
y += array[i];
} else {
y = array[i];
}
if (y > sum) {
sum = y;
}
}
return sum;
}
由于只有一层循环,时间复杂度降为O(n)