地址: https://leetcode-cn.com/problems/maximum-subarray/
给定一个整数数组 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 输入数组是 [-2,1,-3,4,-1,2,1,-5,4] ,我们可以求出以下子问题:
- 子问题 1:经过 -2−2 的连续子数组的最大和是多少;
- 子问题 2:经过 11 的连续子数组的最大和是多少;
- 子问题 3:经过 -3−3 的连续子数组的最大和是多少;
- 子问题 4:经过 44 的连续子数组的最大和是多少;
- 子问题 5:经过 -1−1 的连续子数组的最大和是多少;
- 子问题 6:经过 22 的连续子数组的最大和是多少;
- 子问题 7:经过 11 的连续子数组的最大和是多少;
- 子问题 8:经过 -5−5 的连续子数组的最大和是多少;
- 子问题 9:经过 44 的连续子数组的最大和是多少。
一共 9 个子问题,这些子问题之间的联系并没有那么好看出来,这是因为 子问题的描述还有不确定的地方(这件事情叫做「有后效性」,我们在本文的最后会讲解什么是「无后效性」)。
例如「子问题 3」:经过 -3−3 的连续子数组的最大和是多少。 「经过 -3−3 的连续子数组」我们任意举出几个:
- [-2,1,-3,4] ,-3−3 是这个连续子数组的第 3 个元素;
- [1,-3,4,-1] ,-3−3 是这个连续子数组的第 2 个元素; 我们不确定的是:-3−3 是连续子数组的第几个元素。那么我们就把 -3−3 定义成连续子数组的最后一个元素。在新的定义下,我们列出子问题如下:
- 子问题 1:以 -2−2 结尾的连续子数组的最大和是多少;
- 子问题 2:以 11 结尾的连续子数组的最大和是多少;
- 子问题 3:以 -3−3 结尾的连续子数组的最大和是多少;
- 子问题 4:以 44 结尾的连续子数组的最大和是多少;
- 子问题 5:以 -1−1 结尾的连续子数组的最大和是多少;
- 子问题 6:以 22 结尾的连续子数组的最大和是多少;
- 子问题 7:以 11 结尾的连续子数组的最大和是多少;
- 子问题 8:以 -5−5 结尾的连续子数组的最大和是多少;
- 子问题 9:以 44 结尾的连续子数组的最大和是多少。
我们加上了「结尾的」,这些子问题之间就有了联系。我们单独看子问题 1 和子问题 2:
- 子问题 1:以 -2−2 结尾的连续子数组的最大和是多少;
以 -2结尾的连续子数组是 [-2],因此最大和就是 -2。
以 1 结尾的连续子数组有 [-2,1] 和 [1] ,其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。−2+1=−1<1 ,因此「子问题 2」 的答案是 1。 大家发现了吗,如果编号为 i 的子问题的结果是负数或者 00 ,那么编号为 i + 1 的子问题就可以把编号为 i 的子问题的结果舍弃掉(这里 i 为整数,最小值为 1 ,最大值为 8),这是因为:
- 一个数 a 加上负数的结果比 a 更小;
- 一个数 a 加上 00 的结果不会比 a 更大;
- 而子问题的定义必须以一个数结尾,因此如果子问题 i 的结果是负数或者 0,那么子问题 i + 1 的答案就是以 nums[i] 结尾的那个数。
接下来我们按照编写动态规划题解的步骤,把「状态定义」「状态转移方程」「初始化」「输出」「是否可以空间优化」全都写出来。
dp[i]:表示以 nums[i] 结尾 的 连续 子数组的最大和。 说明:「结尾」和「连续」是关键字。
根据状态的定义,由于 nums[i] 一定会被选取,并且以 nums[i] 结尾的连续子数组与以 nums[i - 1] 结尾的连续子数组只相差一个元素 nums[i] 。
假设数组 nums 的值全都严格大于 00,那么一定有 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] 以后值不会变大。于是 dp[i] 「另起炉灶」,此时单独的一个 nums[i] 的值,就是 dp[i]。 状态转移方程还可以这样写,反正求的是最大值,也不用分类讨论了,就这两种情况,取最大即可,因此还可以写出状态转移方程如下: dp[i]=max{nums[i],dp[i−1]+nums[i]}
dp[0] 根据定义,只有 1 个数,一定以 nums[0] 结尾,因此 dp[0] = nums[0]。
- 这里状态的定义不是题目中的问题的定义,不能直接将最后一个状态返回回去;
- 这里状态的定义不是题目中的问题的定义,不能直接将最后一个状态返回回去;
- 这里状态的定义不是题目中的问题的定义,不能直接将最后一个状态返回回去。
这个问题的输出是把所有的 dp[0]、dp[1]、……、dp[n - 1] 都看一遍,取最大值。
作者:liweiwei1419 链接:https://leetcode-cn.com/problems/maximum-subarray/solution/dong-tai-gui-hua-fen-zhi-fa-python-dai-ma-java-dai/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
lens = len(nums)
if lens == 0:
return 0
dp = [0 for i in range(lens)]
dp[0] = nums[0]
for i in range(1,lens):
dp[i] = max(dp[i-1]+nums[i], nums[i])
return max(dp)