最大子序列求和_最大子序列和问题 一步一步到最优
在《数据结构和算法分析
C++描述》上看到了一个例子。看过之后,我就在想,这是怎么一步一步的递推出来的,想了好长时间,才整理成这篇博文。
问题描述:
给定一个整数序列,a0, a1, a2, …… ,
an(项可以为负数),求其中最大的子序列和。如果所有整数都是负数,那么最大子序列和为0;
例如:对于序列-2, 11, -4, 13, -5, –2。 所求的最大子序列和为20(从11到13,即从a1到a3)。
用于测试下面代码的的主函数代码如下:(注意要更改调用的函数名)
int main(int argc, char **argv)
{ vector a;
a.push_back(-2);
a.push_back(11);
a.push_back(-4);
a.push_back(13);
a.push_back(-5);
a.push_back(-2);
int result;
result = maxSubSum1(a); //在这里更改调用的函数名 cout<
}
方法一:对所有的子序列求和,在其中找到最大的
这是最容易想到的,也是最直接的,就是对所有的子序列求和,代码如下:
int maxSubSum1( const vector &a )
{ int maxSum = 0;
for(int i=0; i
{ for(int j=i; j
{ int thisSum =0;
for( int k=i; k<=j; k++ )
{ thisSum += a[k]; }
if(thisSum>maxSum)
{ maxSum = thisSum; }
}
}
return maxSum;
}
分析:
① 三层循环嵌套,时间复杂度为O(n^3);
② 包含有大量的重复计算,例如i=1时: 当
j=3,则计算a1+a2+a3;j=4,则计算a1+a2+a3+a4;其中a1+a2+a3是重复计算的。
另一种思路:上面的方法在每一次循环中,固定i,并把a[i]当做起点,下面的方法将a[i]当做终点。
int maxSubSum1_2( const vector &a )
{ int maxSum = 0; for(int i=0; i
{ for(int j=0; j
{ int thisSum =0; for(int k=j; k<=i; k++)
{ thisSum +=a[k]; //从节点j开始 累加到节点i
if(thisSum>maxSum)
{ maxSum = thisSum; }
}
}
}
return maxSum; }
方法二:从某点开始的所有序列中,找最大的
如果你意识到,子序列总要有一个位置开始,那么变换一下循环方式,只要求出在所有位置开始的子序列,找到最大的。代码如下:
int maxSubSum2( const vector &a )
{ int maxSum = 0;
for(int i=0; i
{ int thisSum =0;
for(int j=i; j
{ thisSum +=a[j]; //从节点i开始 累加到结尾
if(thisSum>maxSum)
{ maxSum = thisSum; }
}
}
return maxSum;
}
分析:
① 两层循环嵌套,时间复杂度为O(n^2);
②
虽然比方法一要少,但同样包含重复计算。例如:当i=1时,要计算a1+a2+a3+a4+……;当i=2时,要计算a2+a3+a4+……;其中a2+a3+a4+……是重复的。
注意:下面是一个错误的方法,因为它的起始点固定了,每次都从a0开始,是不能保证遍历所有的子序列的。
int maxSubSum2_2( const vector &a )
{ int maxSum = 0; for(int i=0; i
{ int thisSum =0;
for(int j=0; j<=i; j++ )
{ thisSum +=a[j]; //从节点0开始 累加到节点i
if(thisSum>maxSum)
{ maxSum = thisSum; }
}
}
return maxSum;
}
如果希望将固定终点,那么计算的时候就要从终点开始,依次往前累加。代码如下:
int maxSubSum2_3( const vector &a )
{ int maxSum = 0; for(int i=0; i
{ int thisSum =0;
for(int j=i; j>=0; j-- )
{//从节点i开始,向前累加到结尾0
thisSum +=a[j];
if(thisSum>maxSum)
{ maxSum = thisSum; }
}
}
return maxSum;
}
方法三:从某一个正数开始
第一步:
到目前为止,题目的信息你只用到了:“最小子序列之和为0”(若有一项大于0,那么子序列的和一定大于或等于该项,也就大于0;因为若所有项都是负数,那么结果为0
如果你再挖掘一下题意:你就会发现,如果a[i]是负的,那么a[i]一定不是最终所有结果子序列的起始点。代码可以改造为:
int maxSubSum3_1( const vector &a )
{ int maxSum = 0;
for(int i=0; i
{ //(相对方法2,新增)如果a[i]<=0,那么a[i]一定不是所要求的起点,所以直接跳过去(利用for循环中有i++)
if( a[i]<=0 )
{ continue; }
int thisSum =0;
for(int j=i; j
{ thisSum +=a[j];
if(thisSum>maxSum)
{ maxSum = thisSum; }
}
}
return maxSum;
}
第二步:
如果你又再一步发现:任何负的子序列,不可能作为最优子序列的前缀。
又因为上一步已经保证,序列以正数开头a[i]>0,所以若a[i]到a[j]之间元素的序列和
thisSum<=0时,则i+1和j之间元素不会为最优子序列的前缀,可以让i=j,即不需要判断在i和j之间元素开头。代码如下
int maxSubSum3_2( const vector &a )
{ int maxSum = 0;
for(int i=0; i
{ //(相对方法2,新增)如果a[i]<=0,那么a[i]一定不是所要求的起点,所以直接跳过去(利用for循环中有i++)
if( a[i]<=0 )
{ continue; }
int thisSum =0;
for(int j=i; j
{ thisSum +=a[j];
if(thisSum>maxSum)
{ maxSum = thisSum; }
else if( thisSum <= 0 )
{ //(相对方法3_1 新添) thisSum = 0; i = j; }
}
}
return maxSum;
}
第三步:
如果你又进一步发现:因为要求序列开始元素大于0,若以a[i]开头的序列,a[i]>0,那么可以知道,所求的最终子序列一定不会以a[i+1]开始,
因为若到相同的元素终止,那么从a[i]开始序列,一定大于从a[i+1]开始的序列。因为s[i, k]=s[i+1,
k]+a[i]
例如:a1+a2+a3>0,而又由于这时a1>0,
那么所求子序列一定不会以a2开始,因为从a1开始会更大。
更进一步,如若一个子序列thisSum>0(其中thisSum是从第m项到第n项的和),那么序列一定不会以a[m]和a[n]之间的项开始。
因为一直thisSum第一个元素a[m]是大于0的,且以a[m]开始的所有子序列都是大于0的,因为若存在子序列小于0,就会提前返回了。
例如:若程序执行到thisSum
(为a2+a3+a4),若thisSum>0,则能说明,a2>0,并且a2+a3>0,
a2+a3+a4>0。那么 也可以跳过a[m]和a[n]之间的项,即另 i=j。
int maxSubSum3_3( const vector &a )
{ int maxSum = 0;
for(int i=0; i
{ //(相对方法2,新增)如果a[i]<=0,那么a[i]一定不是所要求的起点,所以直接跳过去(利用for循环中有i++)
if( a[i]<=0 )
{ continue; }
int thisSum =0;
for(int j=i; j
{ thisSum +=a[j];
if( thisSum>0 )
{ //(相对方法3_2 新添)
if(thisSum>maxSum)
{ maxSum = thisSum; }
i = j; //(相对方法3_2 新添)
}
else if( thisSum <= 0 )
{ //(相对方法3_1 新添) thisSum = 0; i = j;
}
}
}
return maxSum;
}
第四步:
最后如果你又了解一些程序结构上的优化的知识,那么你会发现下面的问题:
① 循环的分支可以改变一下,去除嵌套分支结构。
② 判断语句的分支中有共同部分,( i=j ),可以抽取出来。
以上两步以后,循环部分的代码编程 变成:
for(int i=0; imaxSum) { maxSum = thisSum; } else if( thisSum>0 ) { //do nothing } else if( thisSum <= 0 ) { thisSum = 0; } i = j; } }
③ 下面这步非常重要,如果你发现,内层循环的循环变量j 和 外层循环的循环变量i同步增长,那么你是否能够想到,外层循环可能没有存在的必要。在这里到底能不能去除外层循环,取决于外层循环中是否有额外的工作要做。这里的额外工作是是if(a[i]
<=0) 判断语句,如果你能发现内层循环的 if(thisSum <
0)的判断能够替代 if( a[i]<=0 )
的工作。因为thisSum是由a[j]得到的。
到这里,代码就可以神奇的变为如下的形式:常量空间,线性时间
int maxSubSum3_4( const vector &a ) { int maxSum = 0; int thisSum = 0; for(int j=0; jmaxSum) { maxSum = thisSum; } else if( thisSum>0 ) { //do nothing } else if( thisSum < 0 ) { thisSum = 0; } } return maxSum; }
分析:
① 只有一层循环,时间复杂度为O(n)。常量空间,线性时间,这是最优解法。
方法四:分治算法(divide and conquer)
本方法和前面三种方法没有直接关系,只是思路的开阔。
因为最大子序列只可能出现在三个地方:①整个出现在原数组的左半部,或者②整个出现在右半部,
或者③跨越中间、从左半部到右半部。
对于第①种和第②种情况,采用递归的方式可以解决。对于第③种情况,分别求出从中间位置开始的最大值然后相加即可。最后比较这三种情况的结果,三者的最大值就是所要求的结果。
//计算三个整数的最大值 int max3(int i, int j, int k){ return (i>j) ? ((i>k)?i:k) : ((j>k)?j:k); } //分治策略的递归函数 int maxSumRec( const vector &a, int left, int right ){ //终止条件 if( left==right ){ if( a[left]>0 ){ //如果该项大于0,才返回 return a[left]; }else{ //如果该项小于0,返回0 return 0; } } //计算左半部 和 右半部 int center = (left+right)/2; int maxLeftSum = maxSumRec(a, left, center); int maxRightSum = maxSumRec(a, center+1, right); //计算跨越中间 int maxLeftBorderSum=0, leftBorderSum=0; for( int i=center; i>=left; i-- ){ leftBorderSum += a[i]; if(leftBorderSum > maxLeftBorderSum ){ maxLeftBorderSum = leftBorderSum; } } int maxRightBorderSum=0, rightBorderSum=0; for( int i=center+1; i<=right; i++ ){ rightBorderSum += a[i]; if(rightBorderSum > maxRightBorderSum ){ maxRightBorderSum = rightBorderSum; } } //返回三个部分的最大值 return max3(maxLeftSum, maxRightSum, maxLeftBorderSum+maxRightBorderSum ); } int maxSubSum4( const vector &a ) { return maxSumRec( a, 0, a.size()-1 ); }
分析:
① 时间复杂度为O(n*logn)。时间复杂度的递归公式为:T(N)
= 2T(N/2) + N。解方程可以得到 T(N)=O(N*logN)。(计算过程较复杂,没有深究的必要)
总结:
上面罗列的一步一步的演变过程,并不是绝对的因果关系,有可能你根本没有从方法一逐渐的演变,直接就能写出方法三的最后一种方法(最优解法)。
但是如果你没有直接写出来,那么你是否会考虑重构你的代码?其实在逐步追求优化的过程中,那感觉还是非常好的!
最大子序列求和_最大子序列和问题 一步一步到最优相关推荐
- 最大子序列求和_最大子序列和问题
问题描述: 给定一个整数序列,a0, a1, a2, -- , an(项可以为负数),求其中最大的子序列和.如果所有整数都是负数,那么最大子序列和为0: 例如:对于序列-2, 11, -4, 13, ...
- 最大子序列求和_连续子序列最大和与乘积问题的分析
问题描述 给定(可能是负的)整数序列A1, A2,...,AN, 寻找(并标识)使Sum(Ak)(k >=i, k <= j)的值最大的序列.如果所有的整数都是负的,那么连续子序列的最大和 ...
- 最大子序列求和_最大连续子序列和
题外话:本文原文是之前在知乎专栏写的一篇文章,现在读来发现好多地方没有写清楚,所以现在做了些修改,希望把分析过程说的更明白一些.为什么要发布到慕课网来呢,主要是因为个人认为这里的IT氛围更好更专业. ...
- 最大子序列求和_算法——求最大子段和
一.问题描述 给定由n个整数组成的序列(a_1,a_2,-,a_n),最大子段和问题要求该序列形如 的最大值(1≤i≤j≤n),当序列中所有整数均为负整数时,其最大子段和为0. 例如,序列(-20, ...
- c 最大子序列和_最大子序列和暴力法、分治+递归法、妙法
你好,我是goldsunC 让我们一起进步吧! 最大子序列和 Question:给定整数(可能有负数),求的最大值(为方便起见,如果所有整数均为负数,则最大子序列和为0). 示例: IN : [-2, ...
- oracle对某两列求和再求和_分手再狠也不怕,3步让他主动求和
无论你处于何种状态,请你相信,分手不是一场没有重逢的告别 之所以这么说是因为,恋人选择分手9成原因是在一起的感觉没那么好了,譬如,以前你们有说不完的话题,最近好像聊地越来越少:以前你们吵架会忍不住找对 ...
- 算法:最大子序列求和问题
最大子序列求和是指给定一组序列,求所有连续子序列的和中的最大值,例如给定数列: [5,-2,-5,6]最大子序列和是6:[1, 2, -3, 4, -5, 6, 7, 8, -9, 10]最大子序列和 ...
- java实现子序列最大和_“最大子序列和”算法 java
maxSubSum各自是最大子序列和的4中java算法实现. 第一种算法执行时间为O(N^3),另外一种算法执行时间为O(N^2),第三种算法执行时间为O(nlogn),第四种算法执行时间为线性N p ...
- 最长递增子序列 子串_最长递增子序列
最长递增子序列 子串 Description: 描述: This is one of the most popular dynamic programming problems often used ...
最新文章
- ML之Clustering之K-means:K-means算法简介、应用、经典案例之详细攻略
- 网易云信集成视频教程(三):如何通过SDK实现自定义消息?
- 重学JavaScript深入理解系列(六)
- JS学习笔记6-JavaScript 数据类型
- php 中文拼音,php中文转拼音
- springboot mysql事物_SpringBoot事务详细简介
- NanoDet-Plus的学习笔记
- 问题九:C++中::是干嘛用的(域解析操作符)
- Evince 3.7.5 发布,多格式文档浏览器
- window Jconsole链接到CenOS 监控Tomcat
- CRM对于企业管理有哪些突破性价值?
- 德软件开发者否认蓄意植入“心血”安全漏洞
- LMDB:闪电内存映射数据库管理器
- 中公教育python教师_中公教育的教师水平怎么样?
- 解析 数据库 苹果自带地图
- 湖北计算机二级考试时间安排,湖北3月计算机二级考试时间安排
- 混合波束成形| MIMO系统的DFT码本
- java异常-绝对解决! The valid characters are defined in RFC 7230 and RFC 3986
- 【心电监测】理论1-相关医学知识
- 河南大学计算机类保研率,郑州大学、河南大学、河南农业大学2021届保研率