最大子数列求和
今天在别人的博客里最大连续子数列和学到了很多,重点复述一下自己喜欢的两个算法
1
用数组sum[i]表示第1个到第i个数的和,用sum[j] - sum[i-1]表示第i个到第j个这个数列的和,省掉最内层的循环,C语言代码如下:
#include <stdio.h>
//N是数组长度,num是待计算的数组,sum是数组前缀和,放在全局区是因为可以开很大的数组
int N, num[16384], sum[16384];
int main()
{
//输入数据
scanf("%d", &N);
for(int i = 1; i <= N; i++)
scanf("%d", &num[i]);
//计算数组前缀和
sum[0] = 0;
for(int i = 1; i <= N; i++) {
sum[i] = num[i] + sum[i - 1];
}
int ans = num[1]; //ans保存最大子数列和,初始化为num[1]能保证最终结果正确
//i和j分别是枚举的子数列的起点和终点
for(int i = 1; i <= N; i++) {
for(int j = i; j <= N; j++) {
int s = sum[j] - sum[i - 1];
if(s > ans) ans = s;
}
}
printf("%d\n", ans);
return 0;
}
依照博主的意思,还可以再优化一下,用num数组直接去存和,代替sum素组:
#include <stdio.h>
int N, num[16384];
int main() {
//输入数据
scanf("%d", &N);
for(int i = 1; i <= N; i++) {
scanf("%d", &num[i]);//计算数组前缀和
num[i] = num[i] + num[i - 1];
}
int ans = num[1]; //ans保存最大子数列和,初始化为num[1]能保证最终结果正确
//i和j分别是枚举的子数列的起点和终点
for(int i = 1; i <= N; i++) {
for(int j = i; j <= N; j++) {
int s = num[j] - num[i - 1];
if(s > ans) ans = s;
}
}
printf("%d\n", ans);
return 0;
}
2
分而治之,即利用函数的递归调用来解决此问题
用一个函数,将一个数组以中心位置为分割点,分两个部分,答案可以分为三种情况:
1.最大子数列在左边
2.在右边
3.横跨分割点,左边右边各占一部分。
遇到1,2种情况,可以递归,直到出现第三种情况为止。
处理第3种情况:
1.以分割点为起点,分别找出左边和右边的最大值(由于确定了的起点,所以复杂度不高),相加即可。
思路如此清晰,那么我们要做的是只是判断是哪种情况即可,这也并非问题,可以继续用递归调用求出1、2的最大子数列,然后与分割点左右两边的最大值比较即可
int solve(int left,int right,int *num) {
//左值,右值,数组地址
if(left==right) {
//递归结束
return num[right];
}
int mid=right+left>>1;
int lans =solve (right,mid,num);//Left Answer,即left max
int rans = solve (mid+1,left,num);//Right Answer
int sum = 0, lmax = num[mid], rmax = num[mid + 1];
for(int i = mid; i >= left; i--) {
sum += num[i];
if(sum > lmax) lmax = sum;
}
sum = 0;
for(int i = mid + 1; i <= right; i++) {
sum += num[i];
if(sum > rmax) rmax = sum;
}
//答案是三种情况的最大值
int ans = lmax + rmax;
if(lans > ans) ans = lans;
if(rans > ans) ans = rans;
return ans;
}