第一种就是纯粹的暴力枚举起始、终点。 O(n^3)
第二种在第一种的基础上先进行初始化,将以第一个元素为起点,所有元素为终点的所有子列和存储在S数组中,所以在第三层循环中计算子列和是直接用S[j]-S[i-1]即可,这是利用了空间去换时间。O(n^2)
第三种也是O(n^2),但是在第二种的基础上,要先算出非负数所在的下标从而减少计算和的次数,但是效果并不好。
//算法3 O(n^2) //主要是想算出正数的地方 从而减少算和的次数,但是需要先找出所有的正数 int n=0,seq[Max_N]={0};//1 for positive or 0 and 0 for negative int begin=0,end=0; long max_sum = NULL; int len = 0; int pos_list[Max_N]={0}; void getMaxSubSeq(){ if(len==0){//如果全是负数则返回0 max_sum=0; return; }else if(len==1){//如果只有一个非负数 返回的就是它 max_sum=seq[pos_list[0]]; return; }else{//否则我们要对每对儿非负数下标进行计算子列和 for (int j = 0; j<len; j++) {//j是非负数下标1 long sum = 0; int h = 0; for (int k = j+1; k<=len; k++) {//k是非负数下标2 //下面的for可以改写成S[j]-S[i]的形式从而降低至S[j]-S[i] for (int t=pos_list[h]+(h==0?0:1);t<=pos_list[k] ; t++) { sum += seq[t]; } //printf("%d\n",int(sum)); h=k; if (max_sum == NULL || max_sum<sum) { max_sum = sum; } } } } } int main(int argc, const char * argv[]) { scanf("%d",&n); //input seq for (int i = 0; i<n; i++) { scanf("%d",&seq[i]); if(seq[i]>=0){ pos_list[len]=i;//把非负数的位置都记下来 len++;//len是非负数的个数 } } getMaxSubSeq(); cout<<(max_sum)<<endl; return 0; }
//算法4 //分治法 O(nlgn) //此时计算的是A[m,n)的范围内的最大子序列和 int getMaxSumOfSubSeq(int* A,int x,int y){ //递归边界 if(y-x==1) return A[x]; //分治第一步,划分(确定子问题) int middle = x+(y-x)/2;//此处不用(x+y)/2的原因是想要使middle更加接近左端点 //分治第二步 递归求解 //利用了>?运算符取两者中较大的 int maxOfTwoSides = getMaxSumOfSubSeq(A, x, middle) >? getMaxSumOfSubSeq(A,middle , y); //分治第三步 合并 求最终解 int v = 0;//此处v是用来暂时储存连续从中段开始向两边延伸的连续和 int L=[middle-1],R=A[middle];//从中间开始 for (int i=middle-1; i>=x; i--) L >?= (v+=A[i]); v=0;//重复利用临时变量 for (int i=middle; i<y; i++)//注意不要包括y R >?= (v+=A[i]); return maxOfTwoSides >? (L+R); }
思路主要是这样的。
在我们用第二种思路时,第二层循环内部要进行比较S[j]-S[i-1]和当前最大和谁大,其实在这个时候,j是一定的,比谁大的本质就是找S[i-1]最小,所以我们不如去维护目前遇到过的最小的S.
int getMSoSS(int* A,int len){ int index_ofMinS = 0; int S[Max_N]; S[0]=A[0]; //第一步 先算出最小的S在哪里取得 //实验表明,如果S[len-1]是最小的 必须用第二小的才行 所以就不算len-1了 for(int i =1;i<len;i++){ S[i] = S[i-1]+A[i]; if(i<len-1 and S[i]<S[index_ofMinS]) index_ofMinS=i; } //此时已经确定了i-1=index.... int max_sum=S[0]; //从此下标之后开始计算~ for(int j=index_ofMinS+1;j<len;j++){ //gcc 应该是可以写成 max_sum >?= S[j]-S[index_ofMinS];的呀 奇怪~ max_sum = max(max_sum , (S[j]-S[index_ofMinS])); } return max_sum; }
第六种的伪代码描述:在线处理算法 最核心的想法就是 当前和如果是负数就不要进行下去 因为无论后面是什么,都相当于在做减法,不会比最大和高。
厉害啦,是在线算法,在线算法就是说,任何时候停止输入,得到的都是当前的正确结果,所以应该是线性的O(n)算法。
int MaxSubseqSum4( int A[], int N ) { int ThisSum, MaxSum; int i; ThisSum = MaxSum = 0; for( i = 0; i < N; i++ ) { ThisSum += A[i]; /* 向右累加 */ if( ThisSum > MaxSum ) MaxSum = ThisSum; /* 发现更大和则更新当前结果 */ else if( ThisSum < 0 ) /* 如果当前子列和为负 */ ThisSum = 0; /* 则不可能使后面的部分和增大,抛弃之 */ } return MaxSum; }