动态规划:最大连续数组

题目:给定一个整数数组,找出总和最大的连续数列,并返回总和。

这个题目初看不会,看了解答还是不会,又看了解答终于想明白了,关键点就一个 连续数列
什么叫做连续数列,就是连在一起的数据。

例如有一个数列,我们要找其中连续的子序列,假设-1是我们最大连续子序列的最后一个数字,然后我们求出以这个数字为结尾的所有的连续的子序列,然后把每一个子序列求和

最后元素子序列和
原始序列-21-34-121-54
子序列1-1-1
子序列24-13
子序列3-34-10
子序列41-34-11
子序列5-21-34-1-1

然后我们再假设2是最大连续子序列的最后一个元素,再来求一遍每个子序列的和

最后元素子序列和
原始序列-21-34-121-54
子序列122
子序列1-121
子序列24-125
子序列3-34-12-2
子序列41-34-123
子序列5-21-34-121

有点感觉了吧,这其实就是把所有连续的子序列全部罗列一边,然后求出所有的连续的子序列的和。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if nums == []:
            return 0
        dp = []
        for end in range(1 ,len(nums)):
            for start in reversed(range(end+1)):
                dp.append(sum(nums[start:end])+nums[end])
        return max(dp + nums)

同样的我们也可以以某个元素为开始计算连续子序列的和。

动态规划

紧接着就是我们的动态规划出场了,其实有点经验的人看到这个题目就知道使用动态规划,只是转移方程不会写。
加入我们已经知道以第i-1为结尾的最大子序列和

dp[i-1]dp[i]子序列和
原始序列-21-34-121-54
子序列1-21-34-13
子序列2-21-34-125

因为是连续的(包含nums[i]以及前面的)所以直接可以知道dp[i] = dp[i-1]+nums[i],这里有一个很有趣的地方

dp[i-1]nums[i]要不要加上
都是正的加上肯定变大啊,跟前面的子序列连上变成更长的子序列
前面是正的,赶快加上前面的连续子序列,让自己变的大一点
本来就是负的了,再加上dp[i-1]的负数就更小了,不加,独立门户,自己就是连续子序列
自己是正的,为什么要跟前面的负数连在一起,独立门户,自己就是连续子序列

哈哈,咱们的转移方程就出现了,加上去比自己大,那就加上,如果加上去比自己还小了,肯定就不加了啊

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if nums == []:
            return 0
        for i in range(1 ,len(nums)):
        	nums[i] = max(nums[i-1]+nums[i],nums[i])
        return max(nums)

这个问题还可以更精简,如果前面是正的,咱们就加上,如果前面是负的,那么当前的最大子序列和就是自身nums[i]

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if nums == []:
            return 0
        res,s = -float('inf'),0
        for v in nums:
            s += v
            res = max(res,s)
            if(s < 0): s = 0
        return res

分治法

其实做这个题目我就是想要学习一下分治算法,没想到前面写了这么多。分治求解的思想非常的简单,我们的连续子序列有三种可能

  • 和最大的子序列存在我们整个序列的左侧
  • 和最大的子序列存在我们整个序列的左侧
  • 横跨序列左半部分和右半部分:也就是说,中间这个数字是左边子序列的结尾,是右边子序列的开始,我们算一下以这个数字开始和结尾的左右子序列最大和,然后求和即可,说起来有点绕,看图
    比较三者的大小,最大者即为所求的最大子序列和
左边中间右边最大和
最大子序列子这边 max_leftmax_left
以v结尾的和最大的子序列 max_v_leftv以v开始的和最大的子序列 max_v_rightmax_v_left + v + max_v_right
最大子序列子这边 max_rightmax_right

整体就是这个样子的,然后咱们写一个伪代码试试

def maxSubArray(nums):
	l,r = 0,len(nums)
	mid = (l+r) // 2
	# 最大和子序列在左侧
	max_left = maxSubArray(nums[:mid])
	# 最大和子序列在右侧
	max_right = maxSubArray(nums[mid:])
	# # 最大和子序列两侧都包含元素
	# 首先计算以mid为结尾的左侧最大子序列的和,这个最开始的那种方法一模一样啊
	# 即,一个一个元素增加求每一个子序列的和
	left_res,right_res = 0,0
	left_max,rigth_max = -float('inf'),-float('inf')
	# 以mid结束的前面所有连续子序列的和,求其中的最大(这里是去除了mid的值,要不然这里会被计算一次)
	for v in reversed(nums[:mid]):
		left_res += v
		left_max = max(left_max,left_res)
	
	# 以mid开始的前面所有连续子序列的和,求其中的最大 (然后这里还会被计算一次,所以上面去掉了mid)
	for v in nums[mid:]:
		right_res += v
		rigth_max = max(rigth_max,right_res)
		
	max_mid = left_max + rigth_max
	
	return max(max_left,max_right,max_mid)
	
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值