剑指offer 30. 连续子数组的最大和

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)

原文地址:https://www.cnblogs.com/hi3254014978/p/12589314.html