一题多解

1、Leetcode 053 最大子序的问题:

参考:

1、https://zhuanlan.zhihu.com/p/25848393

2、https://blog.csdn.net/weixin_41958153/article/details/81131379

1、遍历的方法:o(n)

首先假设我们已经找到了最大连续和子串在数组中的起始位置(i)和结束位置(j),其中i <= j,即最大和maxSum = a[i] + a[i + 1] + ... + a[j],我们来看看这个子串有什么性质:

1,a[i] > 0,否则我们完全可以去掉a[i]这个元素 而得到一个更大的和;

2, i > 0且a[i - 1] < 0 或 i == 0,下面假设i > 0,这一条性质也是因为如果a[i - 1] > 0的话我们求和时可以加上a[i - 1]这个元素得到一个更大的和;

3, 元素a[i - 1]与它之前的任一元素之间的子串之和sum < 0 ,即对于任何一个m(0 <= m < i - 1),则有a[m] + a[m + 1] + ... + a[i - 1] < 0,这条性质同样可以用反证法证明。

class Solution:
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        #onesum维护当前的和
        onesum = 0
        maxsum = nums[0]
        for i in range(len(nums)):
            onesum += nums[i]
            maxsum = max(maxsum, onesum)
            #出现onesum<0的情况,就设为0,重新累积和
            if onesum < 0:
                onesum = 0
        return maxsum

2、动态规划(推荐)

算法过程:

设sum[i]为以第i个元素结尾的最大的连续子数组的和。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i]
= max(sum[i-1] + a[i], a[i])。可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小
 

class Solution:
 
  
    def maxSubArray(self, nums):  
        """ 
        :type nums: List[int] 
        :rtype: int 
        """  
        length=len(nums)  
        for i in range(1,length):  
            #当前值的大小与前面的值之和比较,若当前值更大,则取当前值,舍弃前面的值之和  
            subMaxSum=max(nums[i]+nums[i-1],nums[i])  
            nums[i]=subMaxSum#将当前和最大的赋给nums[i],新的nums存储的为和值  
        return max(nums)

算法证明:这道题的代码我直接使用了题目数据中的nums数组,因为只要遍历一遍。nums[i]表示的是以当前这第i号元素结尾(看清了一定要包含当前的这个i)的话,最大的值无非就是看以i-1结尾的最大和的子序能不能加上我这个nums[i],如果nums[i]>0的话,则加上。注意我代码中没有显式地去这样判断,不过我的Max表达的就是这个意思,然后我们把nums[i]确定下来。
 

3、分治的方法(还没看懂)

python 分治+动态规划

分治法,最大子序和要么在左半部分,要么在右半部分,要么就横跨两部分(即包括左半部分的最后一个元素,和右半部分的第一个元素)。返回这三种情况的最大值即可。第三种情况,其中包括左半部分最后一个元素的情形,需要挨个往前遍历,更新最大值。包含右半部分的第一个元素的情况类似。总的时间复杂度O(nlogn)
 

class Solution(object):
    def maxSubArray(self, nums):
        #主函数
        left = 0
        #左右边界
        right = len(nums)-1
        #求最大和
        maxSum = self.divide(nums,left,right)
        return maxSum
        
    def divide(self,nums,left,right):
        #如果只有一个元素就返回
        if left==right:
            return nums[left]
        #确立中心点
        center = (left+right)//2
        #求子序在中心点左边的和
        leftMaxSum = self.divide(nums,left,center)
        #求子序在中心点右边的和
        rightMaxSum = self.divide(nums,center+1,right)
        
        #求子序横跨2边的和,分成左边界和和右边界和
        leftBorderSum = nums[center]
        leftSum = nums[center]
        for i in range(center-1,left-1,-1):
            leftSum += nums[i]
            if leftSum>leftBorderSum:
                #不断更新左区块的最大值
                leftBorderSum = leftSum
            
        rightBorderSum = nums[center+1]
        rightSum = nums[center+1]
        for i in range(center+2,right+1):
            rightSum += nums[i]
            if rightSum>rightBorderSum:
                #不断更新右区块的最大值
                rightBorderSum = rightSum
        #左边界的和 + 右边那块的和
        BorderSum = leftBorderSum + rightBorderSum
        return max(leftMaxSum,rightMaxSum,BorderSum)

算法证明:

总的来说还是超级巧妙的。不断的切不断的切数组,把一块数组看成左中右三个部分。实际上这有点像枚举,但我们在枚举时利用了二分的思路,优化了很多。所以枚举当然可以达到我们的目标了,因为我们不断在计算以一定包括中间节点的子序的最大和。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值