此文是小仙在刷leetcode时所参考一位大佬的题解后自己对于这一题的理解。
力扣 ,这是那位大佬的主页,有兴趣的小伙伴可以看看,表达得很清楚。
此文主要是以笔记为主,也是第一次发博客,算法小白对于这一题的理解,若有错误,请指出,感谢感谢。
这个题目如果是一个没接触过算法的人来说估计就得用枚举了(反正我最开始是这样想的)。
首先,我们得弄懂题目,题目让我们求出连续子数组的和最大,注意“连续”,“和最大”,就我自己理解而言的话,这其实既是一个限制,也是一个思路,限制在哪呢?
“连续”就说明了这个题不能简单的进行子问题的分解,得逐层去分析,而“连续”、“最大”,这让我想到了有关递推或递归的思路,也就是最终的问题的解是跟每一个子问题有联系的,这时我们可以尝试着定义子问题。
就题目而言,并未给出很多线索,那我们就直接一点。
第一次定义子问题:
经过每个元素的最大连续数组:(这里我用自己举的例子)
nums = [-9,2,5,8,-4,-6,-9,3,6,8,9],len(nums) = 11
子问题1:经过-9的连续数组的最大和
子问题2:经过2的连续数组的最大和
子问题3:经过5的连续数组的最大和
子问题4:经过8的连续数组的最大和
子问题5:经过-4的连续数组的最大和
......
子问题10:经过8的连续数组的最大和
子问题11:经过9的连续数组的最大和
现在子问题是定义好了,但是会发现,它们之间并无太大的联系,没有一定的确定性,因为我们不知道经过某个元素的该元素到底是连续子数组的第几个元素,这样的话,不确定因素太大。
但是我们可以将子问题重新定义,缩小不可控的范围,让它在一定条件下变得可控。
第二次定义子问题:
nums = [-9,2,5,8,-4,-6,-9,3,6,8,9],len(nums) = 11
子问题1:以-9为尾元素的连续数组的最大和
子问题2:以2为尾元素的连续数组的最大和
子问题3:以5为尾元素的连续数组的最大和
子问题4:以8为尾元素的连续数组的最大和
子问题5:以-4为尾元素的连续数组的最大和
......
子问题10:以8为尾元素的连续数组的最大和
子问题11:以9为尾元素的连续数组的最大和
很明显,现在定义的子问题有一个非常明显的联系,那就是每个子问题与它前面的一个子问题只相差一个元素,就是它本身,这样的话,可操作的空间自然也就变大了。
这里我们设连续数组的和最大为dp[i](i就是每个子问题)
nums[i]为当前子问题的末尾元素。
定义状态,理解每一个子元素的状态
(这里可能表达不太清楚)此刻可以试想一下,倘若我们从第一个子问题开始,一直到最后,是不是将所有元素都遍历了一遍,而dp[i-1]与dp[i]只相差了num[i]这一个元素,如果dp[i-1]<=0。
是不是说dp[i]倘若加上了dp[i-1]反而成为了累赘,所以这时候dp[i]=nums[i],如果dp[i-1]>0,那么对于dp[i]来说,dp[i]=dp[i-1]+nums[i]才是目前dp[i]的最大值。
接下来定义状态方程:
由上可知,
dp[i-1] + num[i] ,dp[i-1] > 0 dp[i] = num[i],dp[i-1] <= 0
其实这就是取临近子问题之间或者全局的max,所以状态转移方程也可写成 dp[i] = max(num[i],dp[i-1]+num[i])
上面的表述可能有点模糊不清,但是接下来的例子可能会稍微好一点。
nums = [-9,2,5,8,-4,-6,-9,3,6,8,9],len(nums) = 11
dp[0] = nums[0] = -9 此时为负数,所以连续数组里面有它的话相当于是个累赘 dp[1] = nums[1] = 2 此时是一个新的连续数组 dp[2] = dp[1] + nums[2] = 7 连续数组继续增大 dp[3] = dp[2] + nums[3] = 15 继续增大 dp[4] = dp[3] + nums[4] = 11 这里开始减小,但是并不代表它最后就不是最大的,有可能所有序列中就它最大 dp[5] = dp[4] + nums[5] = 5 继续减小 dp[6] = dp[5] + nums[6] = -4 变成负数,此时在大部分情况下,它可以舍弃了(因为不可能说全是负数的数组) dp[7] = nums[7] = 3 新的连续数组 .... ... dp[11] = 26 max(dp[i],i=[0,1,2,3,4,...,11]) = dp[11] = 26 可以看到从num[7]开始就是dp就是一直增加到最后,所以数组num[7:]就是最终问题的结果。
接下来就是根据状态方程写代码了:
def num_1(): nums = list(map(int,input().split())) if len(nums) == 0: return 0 dp = [i for i in range(len(nums))] dp = [0] dp[0] = nums[0] for i in range(1,len(nums)): if dp[i-1] > 0: dp[i] = dp[i-1] + nums[i] else: dp[i] = nums[i] return max(dp)
其实还可以根据第2个状态方程:
def num_2(): nums = list(map(int, input().split())) tmp = 0 pd = nums[0] for i in range(len(nums)): tmp = max(nums[i],tmp+nums[i]) pd = max(tmp,pd) return pd
不难看出第2个代码它对于内存的消耗要小写,但是的话,可能要更难理解一些,反正我最开始是想了半天,不过仔细想还是能想通的。
大概就是这样了,对于动态规划,我也是才开始去系统的刷题,解题,可能有许多不足之处,再加上是第一次写博客,所以请见谅。
若有大佬路过,请指出不足之处,谢谢。