20.最大子序和-Leetcode 053(python)

  • 题目描述

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

  • 示例

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。


  • 解决思路一

自己想到的方法是比较简单的。遍历所有的元素,以当前元素为子序列的头,数组中该元素之后的任意元素为子序列的尾,计算每个子序列的和,复杂度为O(N^2),结果超时了,没有通过。

  • 代码一
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        maxsum = nums[0]

        length = len(nums)
        if length == 1:
            return nums[0]
        else:
            for i in range(length):
                onesum = 0
                for j in range(i,length):
                    onesum = onesum + nums[j]
                    if onesum > maxsum:
                        maxsum = onesum

                    
            return maxsum
  • 解决思路二(来自网络)

仍旧是遍历法,用onesum来维护遍历到当前元素时,前边的元素组成的子序和,如果onesum出现小于0的情况,就让它等于0;每次都更新全局最大值。

根据:引用自他人博客

据说这道题是《编程珠机》里面的题目,叫做扫描法,速度最快,扫描一次就求出结果,复杂度是O(n)。书中说,这个算法是一个统计学家提出的。
这个算法如此精炼简单,而且复杂度只有线性。但是我想,能想出来却非常困难,而且证明也不简单。在这里,我斗胆写出自己证明的想法:
关于这道题的证明,我的思路是去证明这样的扫描法包含了所有n^2种情况,即所有未显示列出的子数组都可以在本题的扫描过程中被抛弃。
1 首先,假设算法扫描到某个地方时,始终未出现加和小于等于0的情况。
我们可以把所有子数组(实际上为当前扫描过的元素所组成的子数组)列为三种:
1.1 以开头元素为开头,结尾为任一的子数组
1.2 以结尾元素为结尾,开头为任一的子数组
1.3 开头和结尾都不等于当前开头结尾的所有子数组
1.1由于遍历过程中已经扫描,所以算法已经考虑了。1.2确实没考虑,但我们随便找到1.2中的某一个数组,可知,从开头元素到这个1.2中的数组的加和大于0(因为如果小于0就说明扫描过程中遇到小于0的情况,不包括在大前提1之内),那么这个和一定小于从开头到这个1.2数组结尾的和。故此种情况可舍弃
1.3 可以以1.2同样的方法证明,因为我们的结尾已经列举了所有的情况,那么每一种情况和1.2是相同的,故也可以舍弃。
2 如果当前加和出现小于等于0的情况,且是第一次出现,可知前面所有的情况加和都不为0
一个很直观的结论是,如果子段和小于0,我们可以抛弃,但问题是是不是他的所有以此子段结尾为结尾而开头任意的子段也需要抛弃呢?
答案是肯定的。因为以此子段开头为开头而结尾任意的子段加和都大于0(情况2的前提),所以这些子段的和是小于当前子段的,也就是小于0的,对于后面也是需要抛弃的。也就是说,所有以之前的所有元素为开头而以当前结尾之后元素为结尾的数组都可以抛弃了。
而对于后面抛弃后的数组,则可以同样递归地用1 2两个大情况进行分析,于是得证。


说实话看完到现在也有点糊里糊涂的,拿一个具体数组来进行操作一下对这个过程的理解能更清楚一点,但是解释不了上边这么详细,理解起来就是另一个博主的博文:

当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新清零,不然的话这个负数将会减少接下来的和。

算法复杂度为O(N)


  • 代码二
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        onesum = 0
        maxsum = nums[0]
        
        for i in range(len(nums)):
            #onesum维护当前和
            onesum += nums[i]
            
            #更新当前的全局最大值
            maxsum = max(maxsum,onesum)
            #如果onesum<0,就清空当前的累积和,设为0,但是并不会影响最后的maxsum,因为上一句代码已经在考虑onesum的情况下,把maxsum更新过了
            if onesum < 0:
                onesum = 0
                
        return maxsum
 
  • 解决思路三

如果我们用sum[i]存放以元素i为结尾的最大子序列的和,那么我们每次都只需要比较一下sum[i-1]+num[i]和num[i]的大小。因为对于第i个元素来说,如果以第i-1个元素为结尾的最大的子序列之和已经求得,那么,以第i个元素为结尾且和最大的连续子序列,要么就是以第i-1个元素为结尾的有最大和的子序列加上第i个元素(第i个元素大于0),要么就是第i个元素(sum[i-1]小于0)。即sum[i] = max(sum[i-1]+num[i],num[i])。

由于每次运算只需要用到上一次的运算结果,所以仍旧使用nums数组来存放以当前元素结尾的最大子序列的和,在程序运行的最后,找出nums里边的最大值即可。

算法复杂度为O(N)

  • 代码三
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        #遍历从第二个元素开始,才能有前一个元素的概念存在
        for i in range(1,len(nums)):
            
            subMaxsum = max(nums[i-1]+nums[i],nums[i])
            
            #nums[i]的元素用来存放以第i个元素为结尾的最大子序和
            nums[i] = subMaxsum
        
        #返回所有子序和的最大值
        return max(nums)
 

猜你喜欢

转载自blog.csdn.net/Try_my_best51540/article/details/83475966