算法设计与分析第二章作业
题目描述
给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为0。(要求算法的时间复杂度为O(n)。)
输入格式:输入有两行:第一行是n值(1<=n<=10000);第二行是n个整数。
输出样式:输出最大子段和。
输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20
**思路:**若n=1(即序列中只有1个元素),若该元素大于0,则返回该元素;否则返回0(定义一个序列的最大连续子段和至少为0,若元素全为负数,定义其和为0);
- 该子序列完全落在左半部,即a[0…mid]中,采用递归求最大连续子段和maxLeftSum;
- 该子序列完全落在右半部,即a[mid+1…n-1]中,采用递归求最大连续子段和maxRightSum;
- 该子序列跨越a的中部而占据左右部分,以中间元素amid为界,向左边部分和右边部分扩张,以左边为例,定义leftBorderSum=0,maxLeftBorderSum=0
- 让leftBorderSum从中间让左边累加,如果遇到正数就把正数加上去,并把结果赋值给maxleftBorderSum (表现形式就是如果leftBorderSum > maxLeftBorderSum说明加了个正数);
- 右边同样的道理。最后 maxMidSum = maxleftBorderSum + maxRightBorderSum
- 比较出1,2,3,中所求的数那一个最大即为所求结果。
关键: 以数组中点为界,目标值只能在三处—要么在中点左边(不含中点),要么必须包含中点,要么右边(不含中点)。
伪代码:
int maxSubSum(int *a, int left, int right) {
if (left == right) {
return a[left] < 0 ? 0 : a[left];
}
int centor = (left + right) / 2;
int leftSum = maxSubSum(a, left, centor);
int rightSum = maxSubSum(a, centor + 1, right);
int s1 = 0;
int lefts =0;
for (int i = centor; i >= left; i--) { // 从中点向左找最大字段和
lefts += a[i];
if (lefts > s1) {
s1 = lefts;
}
}
int s2 = 0;
int rights = 0;
for (int i = centor + 1; i <= right; i++) {//从中点向右找最大字段和
rights += a[i];
if (rights > s1) {
s1 = rights;
}
}
int sum = s0 + s1; // 第三种情况的最大子段和
sum = max(sum, leftSum); // 求解三种情况的最大值
sum = max(sum, rightSum);
return sum;
}
int maxSum(int *a, int n) {
return sumSubSum(a, 1, n); // 可以根据数组a自行调整
}
时间复杂度:O(logN)
空间复杂度:O(logN)–来源递归调用栈产生的空间
心得体会:
分治法概念上理解就是将整个问题分解成若干小问题后再分而治之。如果分解得到的子问题相对来说还是太大,则可反复使用分治策略将这些子问题分成更小的同类型子问题,直至产生方便求解的子问题,必要时逐步合并这些子问题的解,从而得到问题的解,类似一个递归过程。分治法是一种设计算法的思想,大大优化了算法的运行时间复杂度,优化了程序,如何熟练使用需要继续进行不断地练习才能感悟。