53. 最大子序和.
题目描述
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [0]
输出:0
示例 4:
输入:nums = [-1]
输出:-1
示例 5:
输入:nums = [-100000]
输出:-100000
提示:
1 <= nums.length <= 3 * 104
-105 <= nums[i] <= 105.
题解
一个很典型的动态规划问题问题。
首先题目只要求返回结果,不需要具体的返回是哪一个最大的连续子数组。这样的问题通常使用动态规划来实现
定义子问题
动态规划的思想是通过解决一个个的子问题然后将解决的一个个子问题的解结合得出总问题的结果
我们不知道和最大的连续子数组一定会选哪一个数,那么我们可以求出 所有 经过输入数组的某一个数的连续子数组的最大和。
例如,示例 1 输入数组是 [-2,1,-3,4,-1,2,1,-5,4] ,我们可以求出以下子问题:
- 子问题 1:以 -2 结尾的连续子数组的最大和是多少;
- 子问题 2:以 1 结尾的连续子数组的最大和是多少;
- 子问题 3:以 -3 结尾的连续子数组的最大和是多少;
- 子问题 4:以 4 结尾的连续子数组的最大和是多少;
- 子问题 5:以 -1 结尾的连续子数组的最大和是多少;
- 子问题 6:以 2 结尾的连续子数组的最大和是多少;
- 子问题 7:以 1 结尾的连续子数组的最大和是多少;
- 子问题 8:以 -5 结尾的连续子数组的最大和是多少;
- 子问题 9:以 4 结尾的连续子数组的最大和是多少。
子问题 1:以 -2−2 结尾的连续子数组的最大和是多少;
以 −2 结尾的连续子数组是 [-2],因此最大和就是 -2。
子问题 2:以 11 结尾的连续子数组的最大和是多少;
以 1 结尾的连续子数组有 [-2,1] 和 [1] ,其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。
-2 + 1 = -1 < 1 ,因此「子问题 2」 的答案是 1。
也就是说如果第i个子问题的结果为负数或者0,那第i+1个问题就可以将第i个问题的答案舍去
- 一个数加上负数的结果比本身更小; 一个数加上 0 的结果不会比本身更大;
- 而子问题的定义必须以一个数结尾,因此如果第i个子问题的结果是负数或者0,那么第i+1个子问题的答案就是以nums[i]结尾的那个数。
定义状态
dp[i]:表示以nums[i] 结尾的连续数组的最大和
状态转移方程(描述子问题之间的联系)
根据状态的定义,由于 nums[i] 一定会被选取,并且以 nums[i] 结尾的连续子数组与以 nums[i - 1] 结尾的连续子数组只相差一个元素 nums[i] 。
假设数组 nums 的值全都严格大于 0,那么一定有 dp[i] = dp[i - 1] + nums[i]。
可是 dp[i - 1] 有可能是负数,于是分类讨论:
- 如果 dp[i - 1] > 0,那么可以把 nums[i] 直接接在 dp[i - 1]
表示的那个数组的后面,得到和更大的连续子数组; - 如果 dp[i - 1] <= 0,那么 nums[i] 加上前面的数 dp[i - 1] 以后值不会变大。此时单独的一个 nums[i] 的值,就是 dp[i]。
以上两种情况的最大值就是 dp[i] 的值,写出如下状态转移方程:
dp[i] =dp[i - 1] + nums[i], if dp[i - 1] > 0
dp[i] = nums[i], if dp[i-1]<=0
记为「状态转移方程 1」。
状态转移方程还可以这样写,反正求的是最大值,也不用分类讨论了,就这两种情况,取最大即可,因此还可以写出状态转移方程如下:
dp[i] = max {nums[i], dp[i - 1] + nums[i]}
记为「状态转移方程 2」。
定义初始值
dp[0] 根据定义,只有 1 个数,一定以 nums[0] 结尾,因此 dp[0] = nums[0]。
最后的dp列表,然后返回其中的最大值即可
代码如下:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
lens = len(nums)
s = 0
res = nums[0]
for i in range(lens):
s = max(nums[i], s + nums[i])
res = max(res, s)
return res