计算之魂1.3 例题总和最大区间问题

一、题目

给定一个实数序列,设计一个最有效的算法,找到一个总和最大的区间。

如[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)

三、小结

计算之魂书中此问题关于分治算法、扫描算法的描述和其他书中的有些出入,最后参考了编程珠玑实现,只是初步实现,如果有错误用例后续纠正。感觉计算机作为一门应用学科,只有自己动手去实现才能稍微理解一些作者想表达的意图。而不是纠结于具体的文字是否准确,或者算法步骤是否‘准确’。如果不学习这些思想,感觉工作中大多数时候只会采取最直观的也就是最“简单”的实现,即第一种方法或稍微思考一下的第二种方法。可这却会导致后续程序维护的复杂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值