挑战面试编程:最大连续子序列和
问题
对于形如:int arr[] = { 1, -5, 3, 8, -9, 6 };的数组,求出它的最大连续子序列和。
若数组中全部元素都是正数,则最大连续子序列和即是整个数组。
若数组中全部元素都是负数,则最大连续子序列和即是空序列,最大和就是0。
方法一
用sum[i,j]表示arr[i]到arr[j]的和,则显然可以通过枚举(i<=j),求出所有的和。
int maxSubArr(int *arr, int n)
{
int i, j, k, sum, maxsofar;
maxsofar = 0;
for (i = 0; i < n; i++)
for (j = i; j < n; j++)
{
sum = 0;
for (k = i; k <= j; k++)
sum += arr[k];
maxsofar = max(maxsofar, sum);
}
return maxsofar;
}
时间复杂度O(n^3)
方法二-a
方法一中,有重复计算的嫌疑,显然sum[i,j]依赖于sum[i,j-1]:sum[i,j]=sum[i,j-1]+arr[j];所以不必每次都arr[i]+arr[i+1]……arr[j]才可得到sum[i,j],只需利用sum[i,j]和sum[i,j-1]的关系即可较少运算。这就是:对过程的解进行记录,以防以后用到,从而减少重复求解。
int maxSubArr(int *arr, int n)
{
int i, j, k, sum, maxsofar;
maxsofar = 0;
for (i = 0; i < n; i++)
{
sum = 0;
for (j = i; j < n; j++)
{
sum += arr[j];
maxsofar = max(maxsofar, sum);
}
}
return maxsofar;
}
时间复杂度O(n^2)
方法二-b
对方法一的另一种优化是:先记录sum[0,j](j=0,1,……,n-1),而sum[i,j]=sum[0,j]-sum[0,i-1];这同样可以减少一层循环。
int maxSubArr(int *arr, int n)
{
int i, j, k, sum, maxsofar;
maxsofar = 0;
int *sumcur = malloc(sizeof(int)*n);
sumcur[0] = arr[0];
for (i = 1; i < n; i++)
sumcur[i] += arr[i];
for (i = 0; i < n; i++)
{
for (j = i; j < n; j++)
{
sum = sumcur[j] - sumcur[i] + arr[i]; //[i, j]段的和
maxsofar = max(maxsofar, sum);
}
}
free(sumcur);
return maxsofar;
}
时间复杂度依然是O(n^2)。
为防止越界,使用sum[i,j]=sum[0,j]-sum[0,i]+arr[i];代替sum[i,j]=sum[0,j]-sum[0,i-1];
运用动态规划的思想,先把整个序列等分成两部分:arr[l,m]、arr[m+1,u](l->lower下界,u->upper上界)。
于是,最大和子序列只可能出现在三处:
- arr[l,m]
- arr[m+1,u]
- 最大和子序列跨越arr[m]
对于1,2是相同的子问题,可递归求解。对于3可使用简单的算法直接求解。
int maxsum3(int *arr, int l, int u)
{
if (l > u) return 0; //zero elements
if (l == u) return max(0, arr[l]); //one element
int i, j;
int m = (l + u) / 2;
int lmax, rmax, sum;
lmax = rmax = 0;
sum = 0;
for (i = m; i >= l; i--)
{
sum += arr[i];
lmax = max(sum, lmax);
}
sum = 0;
for (i = m + 1; i <= u; i++)
{
sum += arr[i];
rmax = max(sum, rmax);
}
return max(lmax + rmax, maxsum3(arr, l, m), maxsum3(arr, m + 1, u));
}
int maxSubArr(int *arr, int n)
{
return maxsum3(arr, 0, n - 1);
}
可以证明它的时间复杂度是O(nlogn)
方法四
该算法只需遍历一次arr,即可得出结果。
逻辑是:
arr[0,i](i=0,1,...,n-1)的连续子序列有很多,特别地考虑其中一种:以arr[i]结尾的子序列。使用maxendinghere表示这种特殊子序列的最大和。我们考虑最大和子序列的组成特点,有且仅有两种:
- 与arr[0,i-1]相同。
- 以arr[i]结尾。
maxsofar记录的是arr[0,i]的最大和子序列。显然
,每遍历一个元素,就得更新maxsofar
。
int maxSubArr(int *arr, int n)
{
int maxendinghere, maxsofar;
maxendinghere = maxsofar = 0;
int i;
for (i = 0; i < n; i++)
{
maxendinghere = max(0, maxendinghere + arr[i]);
maxsofar = max(maxsofar, maxendinghere);
}
return maxsofar;
}
时间复杂度O(n)。
深入思考:如何确定最大和子序列的构成呢?也就是确定arr[i,j]中的i,j。
所有内容的目录