输入:n个互相没有关联的数字(正负随机)
输出:该数组中连续数字的最大和
如在数组3 -4 5 2 -5 5 9 -9 -2 8中,连续数字最大和为5 2 -5 5 9这个数字序列的和,最大和为16
一、简单迭代算法
遇到这种问题,头脑中冒出的最直接最简单的就是这种算法。用一个双重循环,一个代表起始位置,一个标注末尾,计算其中元素的和,在与最大值比较,得出新的最大值。
-
for(int i = 0;i<N;i++)
-
{
-
int sum = 0;
-
for(int j = i;j<N;j++)
-
{
-
sum += arr[j];
-
max = MAX(sum,max);
-
}
-
}
对于输入规模n来说,该解法会执行n^2步,所以该算法为平方时间。
二、记录数组和的迭代算法
我们可以用一个数组sum[i],代表前i个整数和这样一种状态。所以后一个状态就由前一个状态加arr[i]得到,而两个状态相减则得到了两个整数中间数的和。
-
for(int i = 1;i<N;i++)
-
sum[i] = sum[i-1]+arr[i];
-
-
for(int i = N-1;i>=0;i--)
-
for(int j = i;j>=0;j--)
-
{
-
int sum1 = sum[i] - sum[j];
-
max = MAX(max,sum1);
-
}
对于输入规模n来说,该解法会执行n^2步,所以该算法仍然为平方时间。
三、分治算法
将一个数组的最大和问题转换为两个数组的最大和问题,然后递归的找出两个向量的最大和。不过要考虑一种情况就是和最大的那一组数组可能跨越两个子数组的边界,所以需要对边界的数组进行处理。对于跨越边界的问题,分别在两个数组中从边界开始,计算边界的最大和,再将两个边界最大和相加,与子数组的最大和比较。
-
float maxsum3(int l,int u)
-
{
-
if(l>u)
-
return 0;
-
if(l == u)
-
return MAX(0,arr[l]);
-
int m = (l+u)/2;
-
-
int lmax = 0,sum = 0;
-
for(int i = m;i>=l;i--)
-
{
-
sum += arr[i];
-
lmax = MAX(lmax,sum);
-
}
-
-
int rmax = 0;
-
sum = 0;
-
for(int i = m+1;i<=u;i++)
-
{
-
sum += arr[i];
-
rmax = MAX(rmax,sum);
-
}
-
-
return max((float)lmax+rmax,maxsum3(l,m),maxsum3(m+1,u));
-
}
可以看到子数组求值的方向都是从边界(中间)向两边展开的。
该算法每次执行n次操作,递归总共有log n次,所以时间复杂度为O(n lon n)。
四、扫描算法
在这个问题中,正负数随机出现,所以我们默认在一个较长的数组里,最大和是大于零的。对一个元素不多的数组进行直觉运算时,我们总是习惯于从左往右开始扫描数字,当发现几个数字的和小于0时就直接略过前面的数字,从新位置开始扫描,并记录下出现过的最大值,直到扫描完整个数组。1977年,当这个问题(模式匹配)被布朗大学的Grenander提出时,他独立设计出了两个平方算法。后来他将问题讲给Michael Shamos听,后者提出了分治算法。那时,他们俩都认为没有更好的算法了,直到后来Michael Shamos在卡耐基——梅隆大学的一次研讨会上提起了这个问题,而参会的一个统计学家Jay Kadane在一分钟内就设计除了线性时间的扫描算法。不过这下,应该不会再有更优的算法了,毕竟任何算法都至少要花费O(n)的时间。
-
int maxsofar = 0,maxendinghere = 0;
-
-
for(int i = 0;i<N;i++)
-
{
-
maxendinghere = MAX(maxendinghere+arr[i],0);
-
printf("%d %d",i,maxendinghere);
-
maxsofar = MAX(maxsofar,maxendinghere);
-
printf(" %d\n",maxsofar);
-
}
-
printf("%d",maxsofar);
对于{3,-4,2,5,-5,5,9,-9,-2,8}这个数组,过程是这样的:
线性时间的算法,效率嘛,谁用谁知道。