动态规划之最大和子数组
给定一个整数数组 nums ,找到一个具有最大和的子数组(子数组最少包含一个元素),返回其最大和
输入: [-2,-1,-3,4,-1,2,1,-5]
输出: 6
解释: 连续子数组 [4,-1,2,1]
的和最大,为 6。
解题思路
使用动态方程解题就需要题目符合动态方程解法,动态方程的两个思路:
- 将大的问题拆解成小一点问题,小问题和大问题的解决思路是类似的
- 小问题之间的关系能完成大问题的最优解
建立数学模型
求数组arr中最大和的子数组,建立一个子数组和的列表dp。其中dp[i]代表着以第i-1个元素结尾的数组的最大和。
一般情况下,我们可以用dp[i]代表以第 i 个元素结尾的子数组的最值,而递推关系就是考虑和dp[i-1]之间的关系
边界值
dp[i]代表这以第i-1个元素结尾的子数组的最大和,所以dp[0]就是边界值。当数组只有一个元素时,该元素就是数组的最大和
状态转移方程
状态转移方程是最难写的,但是我们要迎难而上。下面分析一下该状态如何转移。
这里用dp[i]代表以第 i 个元素结尾的子数组的最大和,则max(dp[i])就是要求的最终结果,那么关键是如何写出递推关系式。
转化后模型:
求i=7结尾的最大和。求i=7时,要知道前面的前一个dp[7-1]的值。比较 dp[6] + nums[7] 和 nums[7] 的大小,大的那一个就是dp[7]的值,也就是以 第7个元素结尾的子数组的最大值。
这里求8个元素的最大和的子数组,这个大问题可以拆解成小问题,即求8个元素的最大和的子数组,继续拆解可以为求7个元素的最大和的子数组,一直到求1个元素的最大和子数组。
-2,-1,-3,4,-1,2,1,-5
-2,-1,-3,4,-1,2,1
-2,-1,-3,4,-1,2
-2,-1,-3,4,-1
-2,-1,-3,4
-2,-1,-3
-2,-1
-2
小问题之间的联系,可以完成大问题的最优解。
-2 最大和就是-2
-2,-1 以-1结尾的最大和为-1,因为前i-1个元素+i元素的最大值为负数,所以最大值为当前-1
-2,-1,-3 以-3结尾的最大和为-3,前2个元素+i元素的最大值为-3,相比较当前值-3,最大值就是-3
-2,-1,-3,4 以4结尾的最大和为4,前3个元素的最大和-3为负数,但没第4个元素,所以最大的和为4
-2,-1,-3,4,-1 以-1结尾的最大和为3,前4个元素的最大和为4,所以加上当前值能变大,最大和为3
-2,-1,-3,4,-1,2 以2结尾的最大和为5,前5个元素的最大和为3,所以加上当前值能变大,最大和为5
-2,-1,-3,4,-1,2,1 以1结尾的最大和为1,前6个元素的最大和为5,加上当前值为6,
-2,-1,-3,4,-1,2,1,-5 以-5结尾的最大和1,前7个元素的最大和为1,大于0,所以加上当前元素能变大,最大和为1
统计出所有以第i个元素结尾的最大和为
-2 | -1 | -3 | 4 | -1 | 2 | 1 | -5 |
---|---|---|---|---|---|---|---|
-2 | -1 | -3 | 4 | 3 | 5 | 6 | 1 |
可以看出当以1结尾时,可以获得最大和的子数组,值为6
明显的,因为我们考虑的子数组以nums[i]结尾,那么该数组一定是包含nums[i]这个元素的,因此需要考虑两种情况:即nums[i]单独成为一段还是与前面的dp[i-1]一起构成子数组,因此,可以得到如下的递推关系式:
dp[i]=max(dp[i-1]+nums[i],nums[i])
或者说:
dp[i-1]代表着前i-1个元素组成的子数组的最大和,那么加上第i个元素时就有两种情况:
- 第i个元素 大于
dp[i-1]+arr[i]
,那么前i个元素的最大值为第i个元素的值 - 第i个元素 小于
dp[i-1]+arr[i]
,那么前i个元素的最大值为前i-1个元素的值 + 第i个元素
dp[i] = max{arr[i], dp[i-1]+arr[i]}
python代码
input_list = [-2,-1,-3,4,-1,2,1,-5]
length = len(input_list)
dp = [0] * length
# 边界值,如果只有一个元素,则第一个元素的最大值就是自身
dp[0] = input_list[0]
for i in range(1,len(input_list)):
# if dp[i-1] + input_list[i] > input_list[i] ---> dp[i-1] > 0。或许前一种写法更容易理解
if dp[i-1] > 0:
# 如果前i-1个元素的最大值大于0,那么加上当前就能使得以当前值为结尾的最大和变大
dp[i] = dp[i-1] + input_list[i]
else:
# 如果前i-1个元素的最大值小于0,那么加上当前就能使得以当前值为结尾的最大和变小。以当前值为结尾的最大和就是自身组成的数组
dp[i] = input_list[i]
print(dp)
print(max(dp))
又可以写成:
input_list = [-2,1,-3,4,-1,2,1,-5,4]
length = len(input_list)
# 构建一个dp数组,用来存储以第i个元素为结尾的最大子数组之和
dp = [0] * length
# 边界值,如果只有一个元素,则第一个元素的最大值就是自身
dp[0] = input_list[0]
for i in range(1,length):
# 循环数组,比较前i-1个数组最大值+自身 和自身的大小。
# 如果大则表明前i-1个数组是正数,则可以继续加上第i个
# 如果小则表明前i-1个数组是负数,那么相加肯定更小,所以以自身为新起点继续往下走
dp[i] = max(dp[i-1]+input_list[i],input_list[i])
print(max(dp))
小结
最长连续子序的解法精华在于 维护了一个dp数组,该数组中的每一个元素都代表着前i-1个元素能够组成的最大值,只需要比较前i-1个元素+第i个元素的值与第i个元素的值的大小,就能得到第前i个元素能够组成的子数组的最大值。