第一课, 讲了最大子序列和的算法。 从O(n^3) 逐步讲解到O(n) 的算法。
O(n^3) 和 O(n^2) 的算法非常直白,无需赘述。
O(n log n)的分治法,值得去实现, 锻炼一下代码能力。
主要思路是: 二分的时候,目标序列只可能有三种情况: 左边,右边,横跨分界线。
选三者中最大的就好了。
我觉得最核心的就是怎么处理 横跨分界线的情况,这个是我一开始没想出来的。
后来看了别人的做法,就是从中间元素开始向左边求和,求出mid到left中,最大的序列和, 然乎再从mid到right找连续的最大序列和。 两者相加即可。
以 4,-3, 5,-2, -1,2, 6,-2 为例,
分治到 4 的时候,返回4, -3的话返回0,还有横跨中间的情况也得到4.
那么[4 -3]所得结果就是 4 0 4.
同理[5 -2] 则是 5 0 5
[ 4 -3 5 -2] 的结果4 5 6, 6是怎么来的?从mid到left 得到1, mid向右得到5,加起来得到6
下面是当时参考的别人的代码
int maxSubArray(int nums[], int left, int right){
int maxLeftSum, maxRightSum;
int maxLeftBorderSum, maxRightBorderSum;
int leftBorderSum, rightBorderSum;
if(left == right)
if(nums[left] > 0)
return nums[left];
else
return 0;
int mid = (left + right) / 2, i;
maxLeftSum = maxSubArray(nums, left, mid);
maxRightSum = maxSubArray(nums, mid + 1, right);
maxLeftBorderSum = 0, leftBorderSum = 0;
for(i = mid; i >= left; i--){
leftBorderSum += nums[i];
if(leftBorderSum > maxLeftBorderSum)
maxLeftBorderSum = leftBorderSum;
}
maxRightBorderSum = 0, rightBorderSum = 0;
for(i = mid + 1; i <= right; i++){
rightBorderSum += nums[i];
if(rightBorderSum > maxRightBorderSum)
maxRightBorderSum = rightBorderSum;
}
return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);
}
O(n)的做法需要多一点分析,利用最大序列和的某些特性:
1、负数开头的序列,都不可能是目标序列,因为总可以去掉这开头的负数,使得序列和更大。
2、特性1的推广,任何和为负数的序列都不可能是 目标序列的一部分,因为总可以去掉开头这部分和为负的子序列,获得更大的序列和。
那么求和的时候,一旦部分和是负的,就可以抛弃了,从下一个数开始从零开始累计即可。
int sum = 0, max=0;
for (int i=0; i<n; i++)
{
sum += a[i];
if (sum <=0 ) sum = 0; //前一段序列和不是正数,从头再来
if (sum > max) max = sum;
}
序列和还有一些别的变种,例如要给出最大序列的开头和结尾。
这里涉及一个细节, 序列和为0的子序列可以作为目标序列的开头, 也可以忽略掉。 这个地方只能根据需要自行调整了。
下面的例子是,尽可能取目标序列中开头 和结尾下标最小的那个。
也就是说,开头和为0的都要选,结尾和为0的都不要
for( i=0; i<n; i++)
{
scanf("%d", a+i);
sum += a[i];
e = i;
if( sum < 0)
{
sum = 0;
s = i+1;
e = s;
}
if( sum > max )
{
max = sum;
start = s;
end = e;
}
}