动态规划算法初识——连续子数组的最大和

1. 暴力求解

起因来源于移到编程题—求给出整数数组中子数组(元素连续)的最大和,经过自以为是且愚蠢的笔者的最初的分析发现,任意子数组均有可能,求解无规则可循,因此采取非常暴力的手段,遍历所有的子数组来求解,代码如下:

    def maxSubArray(self, nums: List[int]) -> int:
        sum_max = - sys.maxsize
        size = len(nums)
        for l in range(size):
            sum_sub = 0
            for e in nums[l:size+1]:
                sum_sub += e
                if sum_sub > sum_max:
                    sum_max = sum_sub
        return sum_max

2. 官方求解

结果自然无法满足时间要求。。。久思无果后,查看力扣(LeetCode)官方jyd的题解后,发现代码竟简单如斯,先不要急着看这段代码,看完理论分析之后会一目了然

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

3. 动态规划

官方求解据说使用了动态规划,这个概念笔者偶尔听说,但咱也不懂呀,因此扒了一段笔者认为最核心的思想:

“ 将待求解问题分解成若干个子问题,先求解子问题,然后从子问题的解得到原问题的解,子问题不是互相独立的 ”,

即子问题A的结果可简化子问题B的计算,而子问题的结果可用来解出最终问题。其实有不少经典问题如数字三角形,台阶问题,斐波拉契数列等等,对于帮助理解动态规划更加清晰容易,但为了能更好的理解动态规划,笔者决定还是深入理解以下,动态规划是怎么被用于“连续子数组的最大和”的求解的。

3.1 理论分析

首先,设我们要处理的数组集合为: A = { a 0 , a 1 , . . . , a n − 1 , a n } A = \{a_0, a_1, ... ,a_{n-1} , a_n \} A={a0,a1,...,an1,an}
A的前k项元素为: A k = { a 0 , . . . , a l , . . . , a r , . . . , a k } A_k = \{a_0, ... , a_l, ... , a_r, ... ,a_k\} Ak={a0,...,al,...,ar,...,ak}
其中最大和子数组集合为: A s = { a l , . . . , a r } A_s = \{a_l, ... ,a_r\} As={al,...,ar}, 其和为: S s = ∑ i = l r a i S_s = \sum_{i=l}^r a_i Ss=i=lrai
当一个新的元素 a k + 1 a_{k+1} ak+1,加入时,我们需要在Ak+1中重新确定A,其实这时候我们对于As 做两个可能的操作:

1)扩展: 很显然,当: ∑ i = r + 1 k + 1 a i > 0 \sum_{i=r+1}^{k+1}a_i > 0 i=r+1k+1ai>0时,元素: { a i + 1 , . . a k + 1 } \{a_{i+1}, .. a_{k+1}\} {ai+1,..ak+1}加入As,能使得Ss比原来更大,即诞生新的最大子数组。

2)分割: 为什么分割呢? 因为数组中的负数元素使得丢弃一段元素可能会出现更大的Ss,当 S s + ∑ i = r + 1 k + 1 = ∑ i = l k + 1 ≤ 0 S_s + \sum_{i=r+1}^{k+1} = \sum_{i=l}^{k+1} \le 0 Ss+i=r+1k+1=i=lk+10 时,说明 ∑ A k + 1 ≤ 0 \sum A_{k+1} \le 0 Ak+10 即K+1 可做切割点,若在K+1后面出现一段元素之和大于等于K+1(包括)之前所有元素的最大子数组和,那么K+1之前的元素全部丢弃,在K+1之后的元素中诞生一个新的最大和子数组。

3.2 实例分析

现在我们使用一个数组来实际尝试一下, 数组 [-2 , 1, -3 , 4, -1 , 2 , 1 , -7 , 4, 1] 为例:

在这里每一步计算,我们记录两个值:(1)当前元素中最大和数组的和 Ss;(2)从最大和数组的第一个元素到当前最后一个元素的和Sc。前者为了记录最大和,后者为了记录分割点,则每一步的结果如下(请先忽视Sa ):

kakSsScSa
0-2-2-20
11111
2-31-20
34444
4-1433
52555
61666
7-76-10
84644
91655

在上表中,k = 0,k = 2,k = 7,时均出现了可分割点。k = 1, k = 3, k = 5, k = 6, 均做了扩展。很显然,我们只需要找出Sc 的最大值即为最终答案。为了计算方便,实际操作中,我们对于累积和小于等于0的直接用0替代,即Sa ,下次计算直接与前项相加即可。这样返回到官方示例,可见其代码清晰明了,简单有效(忘掉前面的推导,心中计算过程依然清晰明了,向大佬致敬。。。)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值