一、题目
给定一个实数序列,设计一个最有效的算法,找到一个总和最大的区间。
如[1.5, -12.3, 3.2, -5.5, 23.2, 3.2, -1.4, -12.2, 34.2, 5.4, -7.8, 1.1, -4.9]
总和区间为[4,9],即第5个数 23.2到第10个数 5.4。
二、解法
这道题作者的目的是让我们对算法复杂度产生了解,不同的算法之间存在复杂度优劣,在写代码时最直观的想法写出来的代码效率可能不是最高的。
2.1 三重循环
def getMaxSumIntervel(arrays):
length = len(arrays)
maxSum = arrays[0]
maxIntervel = (0,0)
for left in range(length-1):
for right in range(left, length):
current_sum = sum(arrays[left:right+1])
if current_sum > maxSum:
maxSum = current_sum
maxIntervel = (left,right)
return maxIntervel
这里作者提到在left和right的组合中,计算Sum(l, r)平均要做K/4次加法。K为数组长度length。目前还不知如何求,看有人用数学归纳法得出K/3
2.2 二重循环
def getMaxSumIntervel2(arrays):
length = len(arrays)
maxSum = 0
maxIntervel = (0,0)
for left in range(length - 1):
current_sum = 0
for right in range(left, length):
current_sum += arrays[right]
if current_sum > maxSum:
maxSum = current_sum
maxIntervel = (left, right)
return maxIntervel
由于在三重循环中计算Sum(left,right)有点没必要,因为current_sum = current_sum + arrays[right],只需存储当前计算的current_sum就好,下次遍历的对象加上current_sum即为left,right的总和
2.3 分治算法
分治法,将数组一分为2,再分别讨论,左边,右边以及从中间往左右延展三种情况,具体参考了《编程珠玑》8.3分治算法介绍以及 10分钟分治算法入门_哔哩哔哩_bilibili
def getMaxSumIntervel3(start, end):
length = end - start + 1
if length == 2:
if arrays[start] > arrays[end]:
return arrays[start], (start, start)
else:
return arrays[end], (end, end)
if length == 1:
return arrays[start], (start, start)
middle = start + int((end - start)/2)
maxsum = 0
maxintervel = (0, 0)
left_sum, left_intervel = getMaxSumIntervel3(start, middle-1)
right_sum, right_intervel = getMaxSumIntervel3(middle, end)
#下边为从middler向左右延伸的计算
current_sum = 0
l_max = 0
left = middle
for i in range(middle, start, -1):
current_sum += arrays[i]
if current_sum > l_max:
l_max = current_sum
left = i
current_sum = 0
r_max = 0
right = middle
for i in range(middle, end, 1):
current_sum += arrays[i]
if current_sum > r_max:
r_max = current_sum
right = i
middle_max = l_max + r_max
if middle_max > left_sum and middle_max > right_sum:
maxsum = middle_max
maxintervel = (left, right)
elif left_sum > middle_max and left_sum > right_sum:
maxsum = left_sum
maxintervel = left_intervel
elif right_sum > middle_max and right_sum > left_sum:
maxsum = right_sum
maxintervel = right_intervel
return maxsum, maxintervel
此处arrays为全局变量,具体逻辑为计算左子数组,右子数组的最大总和以及对应区间,再计算从中间往左,右延伸的最大总和及对应区间,再比较三者中的最大者即为最大总和,其区间为最大区间。调用为:
maxsum, maxIntervel = getMaxSumIntervel3(0, len(arrays)-1),复杂度O(nlogn)
2.4 扫描算法
具体参考《编程珠玑》8.4扫描算法中的描述
def getMaxSumIntervel4(arrays):
maxsofar = 0
maxendinghere = 0
maxintervel = (0, 0)
left, right = 0, 0
for i in range(len(arrays)):
if maxendinghere + arrays[i] > 0:
maxendinghere = maxendinghere + arrays[i]
else:
maxendinghere = 0
left = i + 1
if maxendinghere > maxsofar:
maxsofar = maxendinghere
right = i
maxintervel = (left, right)
return maxintervel
按照编程珠玑中需要理解maxendinghere置0的判断即此时累计的总和<0,那就意味着之前的较大总和区间在此处往后累加无法得到最大总和。那此时需将left移至下一位,从下一位开始寻找较大总和。最终得到最大总和及对应区间。计算之魂关于该算法的描述有些复杂,仔细看了发现书中说的复杂情况实现即为上述代码中分段求maxendinghere,然后找到最大区间总和赋值给maxsofar的过程,而书中说的简单情况其实也可以归纳到复杂情况下,所以上述代码才可以覆盖简单情况。书中提到的前向、后向各扫一遍目的其实也是为了获取left和right的位置,实际上根据上述代码left也可以在前向扫描中获得,不需要后向再扫一遍。(上述为个人理解,有不对的地方会修改)。复杂度O(n)
三、小结
计算之魂书中此问题关于分治算法、扫描算法的描述和其他书中的有些出入,最后参考了编程珠玑实现,只是初步实现,如果有错误用例后续纠正。感觉计算机作为一门应用学科,只有自己动手去实现才能稍微理解一些作者想表达的意图。而不是纠结于具体的文字是否准确,或者算法步骤是否‘准确’。如果不学习这些思想,感觉工作中大多数时候只会采取最直观的也就是最“简单”的实现,即第一种方法或稍微思考一下的第二种方法。可这却会导致后续程序维护的复杂。