最大连续子序列和的题目:
给定整数A1,A2,A3,...,An(可能为负整数),求最大的连续子序列和。如果所有数是负数,则和是零。例如:{-2,11,-4,13,-5,2} 答案是20,序列项从第2项到第4项。
此题很多解法,现讨论4种解法。第1种是简单穷举搜索算法,效率特别低。第2种算法是第一种算法的改进,简单观察完成。第3种算法非常高效,但不简单,复杂度是线性的O(n)。第4种算法复杂度是O(N log N)
第1种算法:直接穷举搜索算法 O(n^3)
public static int maxSubSum(int[] a)
{
int maxSum=0,SeqStart=0,SeqEnd=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;
SeqStart = i;
SeqEnd = j;
}
}
}
return maxSum;
}
此算法最低效,最简单。
第2种算法:改进的O(n^2)算法
public static int maxSubSum(int[] a)
{
int maxSum=0,SeqStart=0,SeqEnd=0;
int i,j;
for(i=0; i<a.length;i++)
{
int thisSum = 0;
for(j=i;j<a.length;j++)
{
thisSum += a[j];
if( thisSum > maxSum)
{
maxSum = thisSum;
SeqStart = i;
SeqEnd = j;
}
}
}
return maxSum;
}
此算法考虑之前的子序列和进行了过多的重复运算,将子序列和重复计算值进行复用,之用2重循环,时间复杂度是O(n^2),此算法仍然是简单穷举搜索,只是略微改进而已。
第3种算法:线性算法
public static int maxSubSum(int[] a)
{
int maxSum=0,SeqStart=0,SeqEnd=0;
int thisSum = 0;
for(int i=0,j=0; j<a.length;j++)
{
thisSum +=a[j];
if( thisSum > maxSum)
{
maxSum = thisSum;
SeqStart = i;
SeqEnd = j;
}
else if(thisSum < 0)
{
i = j+1;
thisSum = 0;
}
}
return maxSum;
}
此算法需要观察,第一次观察,前几项和不能为负数,如果是,则应该抛弃,即最大子序列不可能从负数开始,第二次观察,子序列和一旦加入负数就会小于之前的子序列和。即所有与最大连续子序列相邻的子序列的和一定为负。第三次观察,结合定理。(这个不太懂)
定理:对于任意i,设A(i,j)是第一个序列,且S(i,j)<0,那么对于任意的i<=p<=j,且p<=q,那么A(p,q)要么不是最大连续子序列和,要么等于已出现的最大连续子序列。
第4种算法:分治算法 O(N logN)
public static int maxSumRec(int[] a, int left, int right)
{
int maxLeftBorderSum = 0, maxRightBorderSum = 0;
int leftBorderSum = 0, rightBorderSum = 0;
int center = (left + right) / 2;
if (left == right)// 基本的情况,只有一种元素
{
return a[left] > 0 ? a[left] : 0;
}
int maxLeftSum = maxSumRec(a, left, center);// 执行递归调用,计算左半部最大和
int maxRightSum = maxSumRec(a, center + 1, right);// 执行递归调用,计算右半部分最大和
for (int i = center; i >= left; i--)// 计算包括中心边界的左最大和
{
leftBorderSum += a[i];
if (leftBorderSum > maxLeftBorderSum)
maxLeftBorderSum = leftBorderSum;
}
for (int i = center + 1; i <= right; i++)// 计算包括中心边界的右最大和
{
rightBorderSum += a[i];
if (rightBorderSum > maxRightBorderSum)
maxRightBorderSum = rightBorderSum;
}
return max3(maxLeftSum, maxRightSum, maxLeftBorderSum
+ maxRightBorderSum);
}
public static int max3(int maxLeftSum, int maxRightSum, int maxSum)//计算3个数最大的
{
return maxLeftSum > maxRightSum ? (maxLeftSum > maxSum ? maxLeftSum
: maxSum) : (maxRightSum > maxSum ? maxRightSum : maxSum);
}
public static int maxSubsequenceSum(int[] a)
{
return a.length > 0 ? maxSumRec(a, 0, a.length - 1) : 0;
}
最大子序列的和分三种情况:1)它完全在前半部分;2)它完全在后半部分;3)开始时,在前半部分,最后,在后半部分。
需要在这3种情况中,找到最大的值。运用max3()函数即可。
对于3种情况的讨论,我们使用递归运算。
1.递归计算前半部分和,2递归计算后半部分和,3通过循环计算前半部分和,后半部分和,加在一起形成第3种情况的和。(第3种情况下,前半部分和,必然是包含最后一位,后半部分和必然是包含第一位的)
参考文献:数据结构与问题求解(java语言版)