一、最大子序列和?
最大子序列和是指,给定一组序列,如 [-9,1,-5,4,3,-6,7,8,-2],求子序列之和的最大值,对于该序列来说,最大子序列之和为 4+3±6+7+8=16。
这里的子序列要求是连续的,因此也可以称其为连续子数组最大和。
二、四种方法?
1.暴力搜索
def solu1(A):#暴力循环O(n^3)
s=0
for i in range(len(A)):
for j in range(i+1,len(A)+1):
t=sum(A[i:j])#求sum的过程其实是一次循环
if t>s:
s=t
return s
这里因为要求子序列是连续的所以不是求子集问题,只需要两重循环就可以搞定(即下标0开始的所有子序列,下标1开始的所有子序列……),第三重循环是用来计算子序列和的,所以时间复杂度是O(n^3)。
2.分支法
def solu3(A):#分治法
if len(A)<=1:
return A[0]
mid=len(A)//2
L=solu3(A[:mid])
R=solu3(A[mid:])
lr,ll,t=0,0,0
for i in A[mid:]:
t+=i
if t>lr:
lr=t
t=0
for i in reversed(A[:mid]):
t+=i
if t>ll:
ll=t
l=ll+lr
return max(L,R,l)
分支法理解起来可能有点难,说白了就是分成前后两部分,当然了要设好边界条件,然后我们就默认这个函数可以给出我们想要的解
也就是假设我们已经知道了左边最大的子序列L和和右边最大的子序列和R,那么还要研究下最大子序列和会不会出现在横框左右的中间,记横跨的子序列和最大值为ll,则我们的返回值就是max(L,R,l)
所以我们就从mid开始向前和向后遍历,想后遍历就是找到下标mid开始的有着最大和的子序列,并且记录下为lr,同理mid开始向前遍历也是求出有着最大子序列和的子序列,并记录下为ll,那么横框左右的子序列和的最大值就是ll+lr
会不会出现lr或者ll是负数的情况??没关系的,因为如果ll是负数,就会说明横框左右的子序列和一定小于右半段求出来的最小子序列和,所以不影响最终结果。
根据T(n)=2T(n/2)+O(n)可以算出时间复杂度是O(n logn)
3.动态规划
def solu4(A):#动态规划
r=[-sys.maxsize for _ in range(len(A))]
r[0]=A[0]
#定义r[i]为下标范围在0到i中的子序列,以第i位就是A[i]结尾的子序列中的最大子序列和
for i in range(1,len(A)):
r[i]=max(r[i-1]+A[i],A[i])
return max(r)
动态规划最难想的就是如何定义子问题,以及后项和前项的关系了,这里我们定义r[i]为下标范围在0到i中的子序列,以第i位就是A[i]结尾的子序列中的最大子序列和,所以如果我们得到了所有的r[i],那么其中的最大值就是我们要找的结果。
尝试下或者思考下就会发现,r[i]=max(r[i-1]+A[i],A[i]),因为前面是负的就选后者,前面是正的就选前者,总之拿几个数据试一下就会明白了。
因为只扫描了一遍,时间复杂度:O(n)
4.前后遍历(此方法有误请略过)
def solu2(A):#xyd.py前后扫描
index_begin=0
index_end=len(A)-1
t,temp=0,0
for i in range(len(A)):
t+=A[i]
if t>temp:
temp=t
index_begin=i
t,temp=0,0
for i in range(len(A)-1,-1,-1):
t+=A[i]
if t>temp:
temp=t
index_end=i
return sum(A[index_end:index_begin+1])
这个算法是一个朋友想到的,很巧妙,时间复杂度也是O(n),而且很好理解,第一遍扫描从前往后,找到最大的累加和对应的下标,这就是最大子序列的终止下标,第二遍扫描从后往前,同理会找到最大子序列的开始下标,这样就找到了最大子序列,那么求和就是sum了,至于为什么,意会一下,证明的话,我太懒了。
总结
前后遍历这个方法可能网上早就有人想到了,如有雷同,纯属我知识浅薄!