最大子列和问题描述:
给定一串整数列中,求满足子列和最大,并返回最大值。例如(2, -1, 6, 8, -5, 7, -11),其中的满足和最大的子列为(2, -1, 6, 8, -5, 7),最大值为17。
最蠢的方法是枚举法,把所有的子列都跑一遍。
int max_len(int a[], int N){
int i, j, k, thisSum, maxSum = 0;
for(i=0; ifor(j=i; j0;
for(k=i; k<=j; k++)
thisSum += a[k];
if(thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}
复杂度为O(n^3)
,效率很低。
算法一中对j
的循环是每次自加1,此时i
是不变的,而k
循环的作用是累加从i
到j
的子列长度,可见虽然每次只需要再计算一个int
值,但是k
循环依然从最开始累加,没有必要,所以k
循环这里可以优化。
int max_len(int a[], int N){
int i, j, k, thisSum, maxSum = 0;
for(i=0; i0;
for(j=i; j//优化
if(thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}
也就是每次j
移动的时候就判断大小,复杂度为O(n^2)
。
当算法复杂度为O(n^2)
时,我们总想通过分治法等方法将复杂度降为O(nlogn)
,但是这里分治法需要注意的是,并不是所有子问题的解合并就是最后的解了,由于子列可能存在从左边的子问题跨到右边子问题的情况,如下图:
这时候在递归的同时还要加上一层从中间开始向两边延伸的判断并赋值语句。
int max_len(int a[], int left, int right){
int center, i, j, sum, left_sum, right_sum, s1, s2, lefts, rights;
if(left == right){
if(a[left] > 0)
return a[left];
else
return 0;
} else {
center = (left + right) / 2;
left_sum = max_len(a, left, center);
right_sum = max_len(a, center + 1, right);
s1 = 0;
lefts = 0;
for(i=center; i>=left; i--){
lefts += a[i];
if(lefts > s1) s1 = lefts;
}
s2 = 0;
rights = 0;
for(j=center+1; j<=right; j++){
rights += a[j];
if(rights > s2) s2 = rights;
}
if(s1+s2 < left_sum && right_sumif(s1+s2 < right_sum && left_sum < right_sum) return right_sum;
return s1+s2;
}
}
else中的两个for循环是从中间开始,找出两边各自的最大值,然后相加,得出横跨的最大值, 并与左右两边子问题的最大值进行比较。注意一定是从中间开始。
该算法的复杂度为O(nlogn)
。
在线处理算法:
int max_len(int a[], int N){
int i, thisSum = 0, maxSum = 0;
for(i=0; iif(thisSum > maxSum)
maxSum = thisSum;
else if(thisSum < 0) //当前的子列和为负数,无论后面是什么,负数与其相加肯定不能使子列和增大
thisSum = 0; //所以抛弃它,重新计算下一个子列和
}
return maxSum;
}
该算法不容易理解,但是确是效率最高的,复杂度为O(n),因为至少要遍历所有的元素,所以这已经是效率最高的算法了。