动态规划由简及繁(一)
连续子数组最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof
分析:
-
问题拆解
求连续子数组的和的最大值。
假设数组长度为N。
那么如果知道前N-1子数组的和的最大值Maxn-1,那么到第N个数组的最大值就容易求得了。
-
状态定义
一维数组。
设以nums[i]为结尾的最大子数组和为dp[i]。
其实看到有很多博文总结的说是以这个条件,但是我个人思考了一段时间,不知道为什么需要这样。因为一般来说,我们更常见的是用dp{i]去记录到i为止整个数组和的最大值,而不是所谓的 以nums[i]为结尾的子数组和的最大值。
我个人的思考是这样的。如果我们选择前者,那么造成的结果就是我们计算的是非连续的,只是整个数组的和的最大值。
那么我们就需要把整个数组看成很多满足前者的情况,也就是说,需要把整个数组看成很多子数组,每个子数组有每个子数组的最大值,我们遍历取最终的最大值即可。当然子数组需要连续。所以才有了后者的状态定义。
-
转移方程
考虑Maxi和Maxi-1的关系。
- 如果Maxi-1是负数,那么无论nums[i]是正数还是负数,加上一个负数都会比原来更小。所以不要,以当前数字为结尾的最大子数组和应该是自身。
- 如果Maxi-1是正数,nums[i]可正可负,是正数没话说,是负数的话,因为我们需要记录的是以当前数字为结尾的最大子数组和,所以我们需要加上前面的值。(加上正数总会比自身大)。
dp[i] = dp[i - 1] < 0? nums[i]: dp[i - 1] + nums[i];
-
边界条件
-
刚开始的以自己结尾的最大值就是自己没错了。
dp[0] = nums[0];
-
如果数组长度只有0,直接返回0;只有1,第一个值就是最大的了。
-
-
整体代码
class Solution { public int maxSubArray(int[] nums) { if (nums.length == 0) { return 0; } if (nums.length == 1) { return nums[0]; } int[] dp = new int[nums.length]; dp[0] = nums[0]; for (int i = 0; i < nums.length; i++) { dp[i] = dp[i - 1] < 0? nums[i]: dp[i - 1] + nums[i]; } int max = dp[0]; for (int i = 1; i < nums.length; i++) { if (dp[i] > max) { max = dp[i]; } } return max; } }
-
考虑优化
不必存储所有的子数组的最大和,只把子数组和的最大值存起来,每次比对当前累加的值和最大值的大小并且更新最大值就可以了。
class Solution { public int maxSubArray(int[] nums) { int max = nums[0]; int res = nums[0]; for (int i = 1; i < nums.length; i++) { if (res < 0) { res = nums[i]; max = Math.max(max, res); } else { res += nums[i]; max = Math.max(max, res); } } return max; } }