问题描述
给定n个整数(可能为负数)组成的序列a[1], a[2], a[3],…, a[n], 求该序列如a[i]+a[i+1]+…+a[i+k]的子段和的最大值。
可以从下图中看到
下面介绍三种解法
暴力破解法
核心思想:遍历所有的子段,在其中,找到最大的子段
步骤如下:
- 枚举所有可能的子段:从序列中选取所有可能的子段(可能包括单个元素),这是暴力破解的核心步骤。
- 计算子段和:对于每个选定的子段,计算其所有元素的和。
- 比较和更新:保持追踪当前已知的最大和,如果发现一个新的子段的和大于当前已知的最大和,就更新最大和。
- 重复直至结束:重复上述步骤,直到所有可能的子段都被考察完。
给出
给出代码实现
import random
#生成随机数组
def createrandomList(n=100):
return [random.randint(-10, 10) for _ in range(n)]
#求最大子段长度
def getMaxChildSegment(L):
length = len(L) # 获取数组的长度
if length == 0:#如果数组的长度为0的话,就返回None
return None
max = L[0]#将数组中的第一个元素作为最大元素
for i in range(0,length):
temp = 0#每次循环的时候,将temp置为0
for j in range(i,length):
temp += L[j]#将从i到j中的所有值进行累加
if temp > max:#如果,从i到j中,有一段数组的和大于max,就更新max为temp
max = temp
return max#返回max
if __name__ == '__main__':
originalList = createrandomList(10)#创建一个长度为10的数组
print(originalList)#打印数组
max= getMaxChildSegment(originalList)#找到最大子段的长度
print("最大子段为:{}".format(max),end='')#打印最大子段长度
时间复杂度分析:
n+(n-1)+(n-2)+…+1 = n(n+1)/2=O(n^2)
分治算法
**核心思想:**将数组分为两个部分,分别求两边的数组中的最大子段长度和中间的最大子段长度
具体步骤如下:
- 分解:将原始问题分解成若干个规模较小的子问题。在最大子段问题中,我们将序列分成两半,分别考虑左半部分和右半部分的最大子段和,以及跨越中点的最大子段和。
- 求解子问题:递归地求解这些子问题,直到问题规模足够小,可以直接求解。
- 合并:将子问题的解合并成原始问题的解。在最大子段问题中,需要考虑跨越中点的最大子段和,这可以通过分别从中点开始向左和向右扩展,找到包含中点的最大子段。
- 选择最优解:在合并的过程中,选择最优的解作为最终的答案.
给出代码实现
import random
#最大子段求和
def createrandomList(n=100):
return [random.randint(-10, 10) for _ in range(n)]
def getMaxChildSegment(L,start,end):#传递数组L,开始和结尾的下标
if start >= end:#当开始和结尾的下标相等的时候,返回该下标对应元素的值
return L[start]
middle = int((start+end)/2)#将数划分为两个新的数组
rlmax = getMaxChildSegment(L,middle+1,end)#右边的数组为middle+1到end,求右边新数组中的最大子段长度
llmax = getMaxChildSegment(L,start,middle)#左边的数组为start到middle,求左边新数组中的最大子段长度
ltemp = 0
lmax = L[middle]# 将左边元素的最大值设置为L[middle]
for i in range(middle,start-1,-1):#对左边的数组进行遍历,从middle到start
ltemp+=L[i]
if ltemp > lmax:#找到其中最大的子段长度
lmax = ltemp
rtemp = 0
rmax = L[middle+1]#将右边最大的元素设置为L[middle+1]
for i in range(middle+1,end+1):#对右边的数组进行遍历,从middle+1到end
rtemp+=L[i]
if rtemp > rmax:#找到其中最大的子段长度
rmax = rtemp
return max(rlmax,llmax,(rmax+lmax))#比较左右两边的最大子段长度和中间的最大子段长度,返回其中的最大值
if __name__ == '__main__':
originalList = createrandomList(10)
print(originalList)
max = getMaxChildSegment(originalList, 0, len(originalList) - 1)
print("最大子段为:{}".format(max), end='')
时间复杂度分析:
O(nlog(n))
动态规划算法
**核心思想:**在遍历中,对子段进行动态的更新。对于子段,如果加入新元素后,他的子段和大于0,那么就将其保留;加入新元素后,子段和小于0,那么就将其舍弃。
具体步骤如下:
- 定义状态:定义一个状态,用来表示当前的局部最优解。状态通常包括以当前元素结尾的最大子段和,以及全局的最大子段和。
- 递推关系:建立状态之间的递推关系,即如何从之前的状态推导出当前状态。当前元素结尾的最大子段和可以根据前一个元素结尾的最大子段和和当前元素的值来计算,递推关系通常是
“当前状态 = fun(前一个状态 + 当前元素, 当前元素)”。 - 初始化:确定初始状态的值,为第一个元素的值。
- 迭代计算:从第一个元素开始,根据递推关系和初始状态,依次计算每个状态,最终得到全局的最大子段和。
- 记录最优解:在计算过程中,保持追踪全局最大子段和,以及子段的起始和结束位置,以便在结束时找到最优解。
给出代码实现
import random
#最大子段求和
def createrandomList(n=100):
return [random.randint(-10, 10) for _ in range(n)]
def getMaxChildSegment(L):
length = len(L)#获取数组的长度
if length == 0:#如果数组的长度为0,则返回None
return None
start = 0 # 默认第一个数为最大子段
max = L[0] # 将数组中的第一个元素作为最大元素
for i in range(0,length):#对数组进行遍历
if L[i] >= 0:#找到数组中第一个大于等于0的数
start = i#将最大子段的开始下标设置为i
max = L[i] #将最大子段长度设置为L[i]
break#此时退出循环
if L[i] > max:#避免数组中的元素都小于0,从中找到最大的元素
max = L[i]#将max设置为遍历中的最大值
start = i#将最大子段的下标设置为i
p = start#让p指向最大子段开头的位置
temp = 0#将临时变量的值,设置为1
pre_temp = max#将pre_temp的值设置为mx
while(p < length):#当p小于lenght的时候,将p往后移动
temp += L[p]#使用temp累加数组中的元素
if pre_temp < 0 and temp >= pre_temp:#当数组中的元素,加上第i个元素大于不加第i个元素,且pre_temp为小于0的时候,更新temp
temp = L[p]
if temp > max:#比较temp的值和max的值的大小,是否更新
max = temp
pre_temp = temp#跟新pre_temp的值
p+=1#指针后移
return max
if __name__ == '__main__':
originalList = createrandomList(10)
print(originalList)
max_ = getMaxChildSegment(originalList)
print("最大子段为:{}".format(max_),end='')
时间复杂度分析:
由于只经过一遍遍历,所有时间复杂度为O(n)
总结
暴力破解算法:
- 基本思想:穷举所有可能的子段,计算它们的和,然后找到最大值。
优点:简单易懂,实现容易。
缺点:效率较低,时间复杂度高,不适用于大规模数据。
适用情况:对于小规模问题,或者仅用于教学示例。
分治算法:
- 基本思想:将问题划分成子问题,递归地求解子问题,然后将子问题的解合并成原始问题的解。
优点:相较于暴力破解,分治算法在一些情况下能够提升效率。
缺点:需要额外的递归调用和合并步骤,实现相对复杂。
适用情况:适用于需要处理更大规模问题的情况,但仍不是最优解决方案。
动态规划算法:
- 基本思想:通过利用之前计算的状态来高效地计算当前状态,避免了不必要的重复计算。
优点:在时间复杂度上达到了优化,能够高效地解决大规模问题。
缺点:需要设计状态和递推关系,相对于分治算法会更抽象。
适用情况:适用于处理最大子段和问题以及许多其他动态规划问题。