动态规划_最大m子段和

来源:互联网 发布:mac相册看图片不方便 编辑:程序博客网 时间:2024/06/10 07:55

继最大子段和在空间上的推广,那么再来看看她在个数上的延伸。


最大子段和就是最大m子段和问题在m=1时的特殊情况


类似最大字段和中的b(j)

这里假设b(i,j)表示数组a的前j项中i个子段和的最大值,且第i个子段含a[j]



其中b(i,j-1)+a[j]表示第i个子段含a[j],而,表示第i个子段仅含a[j],所以在表示b(i,j)需要从有i-1个子段中选择一个最大子段和的字段,即找到一个合适的t.

初始时b(0,j)=0,(0<=j<=n);b(i,0)=0,(1<=i<=m)


int MaxSum(int m,int n,int *a){if(n<m||m<1)return 0;int **b=new int *[m+1];int i,j;for( i=0;i<=m;i++)b[i]=new int [n+1];for( j=0;j<=m;j++)     //0....mb[j][0]=0;for(int k=1;k<=n;k++)    //1...mb[0][k]=0;for(i=1;i<=m;i++)for(int j=i;j<=n-m+i;j++)<span style="color:#FF0000;"><strong>if(j>i)</strong>{b[i][j]=b[i][j-1]+a[j];for(int k=i-1;k<j;k++)    //从有i-1个子段中选择一个最大子段和的字段if(b[i][j]<b[i-1][k]+a[j])   </span> //表示第i个字段仅含a[j],所以需要得到max{b[i-1][k]},这样的b[i][j]才能表示最大i字段和<span style="color:#FF0000;">b[i][j]=b[i-1][k]+a[j];}else                   //i==j的情况b[i][j]=b[i-1][j-1]+a[j];</span>int sum=0;for( j=m;j<=n;j++)if(sum<b[m][j])sum=b[m][j];printB(b,m,n);return sum;}


例如:a[]={3,-4,2,11,-4,13,-5,-2} m=3


根据以上算法段可得出下表,表中蓝色表示初始化,红色xx表示不存在i>j的情况,即b(i,j)不合实际,紫色o表示该字段仅含一个元素,

例如b(i,j),i=1时,j最多只能为6,然后a[7],a[8]各为一个子段,这样才能构成m=3的子段


重点是绿色数字,表示经过了更新的值。例如绿色2,此时i=2,j=3,根据b(i,j-1)+a[j]=-1+2=1,然后从i-1~j-1,也就是1~2,maxb(i-1,t)+a[j]=0+2=2    由于2>1,所以更新b(2,3)=2

再比如说绿5,1(-1+2)->5(3+2)     绿26,25(12+13)->26(13+13)      绿16,12(1+11)->16(5+11).......



最后在b(3,j)中找最大值,即1,16,12,29,24,24,中,找出29为{3,-4,2,11,-4,13,-5,-2}最大3子段和



算法分析:由此可知该算法时间复杂度为O(m*n^2),空间复杂度为O(m*n)


但是经分析可知计算b[i][j]时只用到了数组b的第i-1行和i行的值,因而算法只要储存b的当前行i和i-1行,没必要储存整个二维数组的值。

改进后算法如下:


int MaxSum(int m,int n,int *a){if(n<m||m<1)return 0;int *b=new int[n+1];int *c=new int[n+1];b[0]=0;for(int k=0;k<=n;k++)     //初始化保存上一轮值的数组c[k]=0;for(int i=1;i<=m;i++){b[i]=b[i-1]+a[i];           //第i个字段包含a[i]c[i-1]=b[i];       //<span style="color:#FF0000;">为什么是c[i-1]=b[i]呢?保存当前最大子段和,因为在下一轮即有2,3个子段时,当前的b[i]会作为b[i-1]来计算新一轮的b[i]</span>int max=b[i];for(int j=i+1;j<=i+n-m;j++)  //第i个字段仅包含a[i]{b[j]=b[j-1]>c[j-1]?b[j-1]+a[j]:c[j-1]+a[j];     //c[j-1]上一轮的最大值,没有的话默认为0c[j-1]=max;          //<span style="color:#FF0000;">非首个成员的话,先是用c[j-1]直接保存上一次最大值,</span>if(max<b[j])                 <span style="color:#FF0000;"> //然后更max,用于下一次,这样的c[]在该轮必定是递增的,</span>max=b[j];}c[i+n-m]=max;//<span style="color:#FF0000;">当然在一轮结束的时候c[]也是要保存当前最大子段和max</span> cout<<"b[] ";printA(b,n);cout<<"c[] "; printA(c,n);cout<<endl; }int sum=0; for(int j=m;j<=n;j++)if(sum<b[j]) sum=b[j];return sum;}

运行结果:


红色圆点表示:c[]被更新的地方,

红色斜杠表示:某个子段首个成员更新的情况,如b[1]=3-->c[0]=3,这是直接更新的,但是在计算出b[2]=-1,先是直接赋值c[2],因为此时的最大字段和就是上一次的3嘛,但有所不同的是,这里我会将max和当前最大字段和b[j]比较一下,然后更新,用于下一次的c[j-1]


由此可知该算法的时间复杂度O(m(n-m),空间复杂度为O(n),当m或者n-m为常数时,时间复杂度便可降为O(n)

0 0