问题描述
给定整数 A 1,A2,…………,An(可能有负数),求 ∑ k = i j ∑^{j}_{k=i} ∑k=ij的最大值(如果全为负数,最大子序列和为0)
思路分析
首先一看到这个题目,最容易想到的方法当然是暴力破解,但是如果不加任何改进,直接在外面用双重循环表示从i到j,里面再用一个循环来求i到j的和,效率是非常低下的,时间复杂度是O(n3)
暴力破解
int maxsubsquence2(int *a,int m){
int i,j,k;
int ans=0,temp=0;
for(i=0;i<m;i++){
for(j=i+1;j<m;j++){
for(k=i;k<j;k++)
temp+=a[k];
if(temp>ans)
ans=temp;
temp=0;
}
}
return ans;
}
如果再仔细想一下我们可以发现,最内层的循环中重复了很多次已经求过了的求和运算,在i相同的情况下,我们完全可以通过上一次循环求到了的结果来进行下一次的求和运算,这样最内层的循环完全可以不要,代码就变成了下面的样子
优化后的暴力破解
int maxsubsquence3(int *a,int m){
int i,j,k;
int ans=0,temp=0;
for(i=0;i<m;i++){
temp=a[i];
for(j=i+1;j<m;j++){
temp+=a[j];
if(temp>ans)
ans=temp;
}
}
return ans;
}
这样一来,时间复杂度就变成里O(n2),效率大大提高,但是平方级的时间复杂度依然很高,那么我们还有什么更高效的方法吗?
其实还有一种更好的O(nlogn)的递归算法,但是写起来比较复杂,基本思想就是把一个序列的分成两个基本等大的序列,然后递归求解,最大的子序列只可能出现在三个地方,左边的序列,右边的序列,和序列的断开处,利用递归可以很轻松的算出左序列和右序列的最大值,因为用递归不断的分成两半,到最后就会分成只剩一个数,如果是正数就直接返回,负数或0就返回0,然后返回的这个数就会变成上一级递归(有两个数)的左序列或者右序列的最大值,依此递推。至于中间序列的情况,只需要从中间开始,往左右两边求和,分别求出左边中间,右边中间的最大序列,然后把两边加起来,就是中间的最大值,然后返回左边序列,中间序列,右边序列中的的最大值。
分治
int MaxSubsequenceSum(int* a,int i,int j){
//基准情况,只剩一个数
if(i==j){
if(a[i]>0)
return a[i];
else
return 0;
}
//中间数的下标
int ave=(i+j)>>1;
int temp=0;
//左边序列最大
int max1=MaxSubsequenceSum(a,i,ave);
//右边序列最大
int max2=MaxSubsequenceSum(a,ave+1,j);
int maxlb=0,maxrb=0;
int i1,j1;
//中间序列最大
//中间左边
for(i1=ave;i1>=i;i1--){
temp+=a[i1];
if(temp>maxlb)
maxlb=temp;
}
temp=0;
//中间右边
for(j1=ave+1;j1<=j;j1++){
temp+=a[j1];
if(temp>maxrb)
maxrb=temp;
}
//返回回最大值
return max(max1,max2,maxlb+maxrb);
}
int max(int a,int b,int c){
if(a>=b&&a>=c)
return a;
else if(b>=a&&b>=c)
return b;
else
return c;
}
那么,还有没有更加简洁而又高效的算法呢?虽然在大多数情况下,算法的高效与它的代码长度或
者理解难度是成正比的,但这道题却是一个例外,他还有一个既简洁又高效的解决方法。
动态规划(?)
int maxsubsquence(int *a,int m){
int ans=0;
int temp=0;
int i;
for(i=0;i<m;i++){
temp+=a[i];
if(temp<=0)
temp=0;
else if(temp>ans){
ans=temp;
}
}
return ans;
}
那么,我们来想一下,为什么这样写是对的,首先如果一个序列是按顺序加起来一直都大于0的序列,那么它都有可能接上后面的一段成为最大序列,那么为什么要加上依顺序和一直这两个限定词呢,因为有可能这个序列一开始的和小于0,但是加上一个更大的正数之后又大于0了,假如这个序列是最大子序列的一部分的话,那么它删去前面那段负的序列毫无疑问会变得更大。这就是为什么当一段序列小于0之后,就把temp的值赋为0的原因。
附(时间复杂度分析)
1.T(m)=
∑
i
=
0
m
−
1
∑
j
=
i
+
1
m
−
1
∑
k
=
i
j
1
∑^{m-1}_{i=0}∑^{m-1}_{j=i+1}∑^{j}_{k=i}1
∑i=0m−1∑j=i+1m−1∑k=ij1
=
∑
i
=
0
m
−
1
∑
j
=
i
+
1
m
−
1
(
j
−
i
+
1
)
∑^{m-1}_{i=0}∑^{m-1}_{j=i+1}(j-i+1)
∑i=0m−1∑j=i+1m−1(j−i+1) =
∑
i
=
0
m
−
1
(
m
−
i
−
1
)
(
m
−
i
−
2
)
/
2
∑^{m-1}_{i=0}(m-i-1)(m-i-2)/2
∑i=0m−1(m−i−1)(m−i−2)/2
=
∑
i
=
0
m
−
1
(
m
2
−
2
m
i
−
3
m
+
3
i
+
i
2
+
2
)
/
2
∑^{m-1}_{i=0}(m^{2}-2mi-3m+3i+i^{2}+2)/2
∑i=0m−1(m2−2mi−3m+3i+i2+2)/2
={(m2-3m)m+m(m-1)(3-2m)/2+m(m-1)*(2m-1)/6+2m}/2
=o(m3)(这里的m和下面的n意思相同,都是输入规模)
2.优化后的与上面类似
3.
T(1)=1;(1是指一个时间单位,指输入的序列长度为一时执行前几行代码所需要的时间)
T(n)=2T(n/2)+O(n)
令n=2k
T(2k)
=2T(2k-1)+2k=2(2T(2k-2)+2k-1)+2k
=2(2(2T(2k-3)+2k-2)+2k-1)+2k
=2kT(1)+2k+2k+…………+2k
=2k(k+1)=nlog2n+1
4.T(n)=O(n)