关于子序列最大和的几种算法

时间:2021-07-07 11:08:10
    所谓最大子列和,就是给出一个序列,找到其中一段连续的子序列,并且该子序列的和最大。
如序列:2 3 1 -5 5 -3 3
显然该序列子序列 2 3 1 有最大和 6
今天,说三种方法来求解此问题。


一,最笨的一种方法
#include<stdio.h>
int main(void)
{
int k,max=0,s=0,f;
int p[1000];
scanf("%d",&k);
for(int i=0;i<k;i++)
scanf("%d",&p[i]);
for(int j=0;j<k;j++)//子序列的左边开头
{
for(int m=j;m<k;m++)//子序列的右边结尾
{
s=0;
for(f=j;f<=m;f++)//每个子序列的区间
{
s=s+p[f];
}
if(s>max)
max=s;
}
}
printf("%d\n",max);
return 0;
}
之所以说是最笨的方法,是因为该程序用的是三重循环,时间复杂度是O(N3)


第二种方法
#include<stdio.h>
int main(void)
{
int k,max=0,s=0,f;
int p[1000];
scanf("%d",&k);
for(int i=0;i<k;i++)
scanf("%d",&p[i]);
for(int j=0;j<k;j++)//子序列的左边开头
{
s=0;
for(int m=j;m<k;m++)//子序列的右边结尾
{
s=s+p[m];
if(s>max)
max=s;
}
}
printf("%d\n",max);
return 0;
}
该方法是上一种方法的改进版本,由上一种方法的先求出区间,再进行累加,改为直接的逐个累加,因此可以少一个循环,时间复杂度降低至O(N2)


第三种方法
#include<stdio.h>
int main(void)
{
int k,max=0,s=0;
int p[1000];
scanf("%d",&k);
for(int i=0;i<k;i++)
scanf("%d",&p[i]);
for(int j=0;j<k;j++)
{
s=s+p[j];//1
if(s>max)//2
max=s;
if(s<0)//3
s=0;
}
printf("%d\n",max);
return 0;
}
该算法不仅代码量少,而且时间复杂度降低到了O(N),对于该算法(特别是if(s<0) s=0; ),应该怎么理解呢?
给定一个序列:
-1 3 2 -7 5 3 -1
 开始执行语句1 得s=-1;不符合语句二,所以直接执行语句3 s=0;
接着 执行1 s=3;max=3;
接着 执行1 s=3+2=5; max=5;
接着 执行1 s=5+(-7)=-2; 因为s<0;所以 执行3,s=0;(语句3的作用就是,当s<0的时候,如果向后加,对于整体的序列,和肯定是减小的,所以索性把前面的序列都舍去,等于另外开辟一个新的序列),此时的max保存的还是最大值,max=5;
接下来跟上面就一样啦 可以理解成求最后三个数构成的子序列的最大和 最大和是5+3=8;
所以 最后max=8


除此之外 还有一个分治法,也可以求解此问题!