30. 连续子数组的最大和
题目描述
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
思路一:
动态规划,dp[i] 表示 以A[i]结尾的最大连续子序列和
设当序列以A[i] 结尾时的和最大,如果序列长度为 1 ,那么dp[i] = A[i],
如果序列长度不为 1, 则序列和为 前面的和 加上 A[i] , 即dp[i] = dp[i - 1] +A[i]; 所以转移方程为: dp = max(A[i], dp[i - 1] + A[i]);
其实最主要看 dp[i - 1] 是大于0 还是小于0, 如果大于0,那肯定要加上前面的序列,如果小于0,则不要前面的序列; 所以状态转移方程可以改为 : dp[i] = dp[i - 1] > 0 ? dp[i - 1] + A[i] : A[i];
再改进后可以改为 dp[i] = Math.max(nums[i], nums[i] + dp[i-1]);
1 class Solution { 2 public int maxSubArray(int[] nums) { 3 // 动态规划,使用一个dp数组,每个dp元素表示以当前元素为子数组的最后一个结点的最大和 4 int len = nums.length; 5 int[] dp = new int[len]; 6 dp[0] = nums[0]; 7 int max = dp[0]; 8 for(int i = 1; i < len; i++){ 9 // 如果加上前面的子数组和比当前元素大,那么加上前面的子数组和,否则不加 10 dp[i] = Math.max(nums[i], nums[i] + dp[i-1]); 11 max = Math.max(max, dp[i]); 12 } 13 return max; 14 } 15 }
leetcode运行时间为2ms,空间为45.2MB
复杂度分析:
时间复杂度:需要遍历一次数组,所以时间复杂度为O(n)
空间复杂度:需要一个和nums数组等大的dp[]数组,所以空间复杂度为O(n)
思路二
对思路一的改进,仔细看思路一的代码,发现dp数组其实不需要的,只需要两个临时变量存储 dp[i]和dp[i-1]即可,所以代码可以修改为:
1 class Solution { 2 public int maxSubArray(int[] nums) { 3 // 动态规划 4 int len = nums.length; 5 int pre = nums[0], cur = 0; 6 int max = pre; 7 for(int i = 1; i < len; i++){ 8 // 如果加上前面的子数组和比当前元素大,那么加上前面的子数组和,否则不加 9 cur = Math.max(nums[i], nums[i] + pre); 10 max = Math.max(max, cur); 11 pre = cur; // 更新子数组和 12 } 13 return max; 14 } 15 }
leetcode运行时间为1ms, 空间为45.4MB
复杂度分析:
时间复杂度:同样需要遍历一遍数组,所以时间复杂度为O(n)
空间复杂度:不需要临时的dp数组,所以空间复杂度为O(1)