问题:
给定(可能有负的)整数
A1,A2,A3,...,AN
, 求
∑jk=iAk
的最大值。
解1:
最先得到的方法大概是使用两层循环,遍历所有的子序列,求和后比较,记录最大者,以及最大者的起点,终点。时间复杂度
O(N2)
, 代码如下:
public int getMaxSumOfSubSeqSlow(int[] inputArr)
{
int max = 0;
int startPoint = 0;
int endPoint = 0;
for(int i=0; i<inputArr.length; i++)
{
int sum = 0;
for(int j=i; j<inputArr.length; j++)
{
sum += inputArr[j];
if(sum > max)
{
max = sum;
startPoint = i;
endPoint = j;
}
}
}
return max;
}
解2:
对任意成为最终解的子序列,都不会有和为负数的前缀。因为若有和为负数的前缀,去掉该前缀的剩余子序列会大于原有子序列,原有子序列即不是最终解,不满足假设的前提。
于是,可以从0开始累加,当累加的和大于现有最大值时,更新最大值。当累加的和小于0时,从下一位开始重新累加。从下一位开始累加不会错过最优解,因前面的每一次被抛弃的子序列,和均为负数,整体的和也是负数。这是
O(n)
的解法。
这种解法只对数据进行一次扫描,不需要存储扫描过的数据,算法可在任意时刻对已读入的数据给出一个最优解。具有这种特性的算法叫做联机算法(on-line algorithm)。仅需要常量空间,并以线性时间运行,几乎是完美的算法。
public int getMaxSumOfSubSeq(int[] inputArr)
{
int max = 0;
int sum = 0;
int startPoint = 0;
int endPoint = 0;
int tmpStartPoint = 0;
for(int i=0; i<inputArr.length; i++)
{
sum += inputArr[i];
if(sum < 0)
{
tmpStartPoint = i+1;
sum = 0;
}
else if(sum > max)
{
startPoint = tmpStartPoint;
endPoint = i;
max = sum;
}
}
return max;
}
该问题有优于解1 的分治解法,时间复杂度为 O(N logN) 。整体思路是:将序列分为大小大致相等的两个子序列,最优解或者在左半边,或者在右半边,或者横跨两者。根据分割点分别向左/右求得最大和,相加即为横跨情况的最大解,该横跨情况的最优解与递归得到的左边最大解,右边最大解比较,即为整个序列的最大解。未编写代码。
若想在有多个最优解时获取所有的解,可将startPoint等改用列表存储。
随机生成100000个数时,对比如下: