问题描述:给定由n个整数(可能为负整数)组成的序列a1,a2,a3,...,an,寻找它的某个连续子段,使得其和最大。如(-2,11,-4,13,-5,-2)最大子段是{11,-4,13}其和为20。
【暴力法】
# 直接遍历,暴力解决
sum=0
a=[-2,11,-4,13,-5,-2]
for i in range(len(a)): # 遍历起点
for j in range(i,len(a)): # 遍历终点
thissum=0
for k in range(i,j+1): # 从起点遍历到终点
thissum+=a[k]
if(thissum>sum):
sum=thissum
besti=i
bestj=j
print("最优值为:",sum)
print("最优解为:{0}至{1}".format(besti,bestj))
暴力法的时间复杂度是:O()
为避免重复计算,可将此算法进行改进。
sum=0
a=[-2,11,-4,13,-5,-2]
for i in range(len(a)):
thissum=0
for j in range(i,len(a)):
thissum+=a[j]
if thissum>sum:
sum=thissum
besti=i
bestj=j
print("最优值为:",sum)
print("最优解为:{0}至{1}".format(besti,bestj))
改进后的时间复杂度为O(n²)
【分治法】
–将序列a[1:n]分成长度相等的两段a[1:n/2]和a[n/2+1:n],分别求出这两段的最大字段和
–则a[1:n]的最大子段和有三种情形:
①a[1:n]的最大子段和与a[1:n/2]的最大子段和相同;// 在左边
②a[1:n]的最大子段和与a[n/2+1:n]的最大子段和相同; // 在右边
③a[1:n]的最大字段和为ai到aj的和,且1<=i<=n/2,n/2+1<=j<=n // 跨了中点
对于①②,直接递归
–对于③,an/2与an/2+1肯定在最优序列中,s1+s2即为出现情形3时的最优值,以n/2为界,左右两边向中间缩,分别找最大子段和
# 分治法
def maxSubSum(a,left,right):
sum=0
if left==right:
if a[left]>0:
sum=a[right]
else:
sum=0
# sum=a[left]>0?a[right]:0
else:
center=(left+right)//2
leftSum=int(maxSubSum(a,left,center)) # 找最左边最大和
rightSum=int(maxSubSum(a,center+1,right)) # 找最右边最大和
# 覆盖了n/2的左半边最大和
s1=0
lefts=0
for i in range(center,left-1,-1):
lefts+=a[i]
if lefts>s1:
s1=lefts
# 覆盖了n/2+1的右半边最大和
s2=0
rights=0
for i in range(center+1,right+1):
rights+=a[i]
if rights>s2:
s2=rights
sum=s1+s2 # 跨了中点情况的最大值
if sum<leftSum:
sum=leftSum
if sum<rightSum:
sum=rightSum
sum=max(sum,leftSum,rightSum) # 求三者最大
return sum
if __name__=='__main__':
a = [-2, 11, -4, 13, -5, -2 ]
left=0
right=len(a)-1
print(maxSubSum(a,left,right))
分治法的时间复杂度是O(nlogn)
【动态规划】
计算b[j]的动态递归式为b[j]=max{b[j-1]+a[j],a[j]} 1≤j≤n
据此,可设计出的最大子段和的动态规划算法如下:
# 动态规划
sum=0
b=0
i=0
a=[-2,11,-4,13,-5,-2]
for j in range(len(a)):
if b>0: # 如果b大于0,就进行累加操作
b+=a[j]
else: # 否则b=a[j],从这里开始累加,利用i来记录这里j的值
b=a[j]
i=j
if b>sum: # 判断最大值为最优值
sum=b
i2=i
j2=j
print("最优值为:",sum)
print("最优解为:{0}至{1}".format(i2,j2))
此动态规划主要记录前一次的值,然后进行最大值比较,此算法的时间复杂度为O(n)