问题
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例: 输入: [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
分析
这个问题的关键在于:如果已经累加的和为负数了,那么不论后面是正数还是负数,累加之后都不可能超过当前的数,所以sum从当前的数重新开始。如果累加的和不为负数,不论后面的数字是正数还是负数,都加进去。因为只有sum > maxSum才有可能重新赋值maxSum。 有可能很疑惑的一点是:累加的已经很大了,但是后面来个一个负数,这个时候不应该累加进去啊?没关心,先累加进去,这次累加之后maxSum并没有改变。有可能再下一次数字就是个很大的正数,加进去之后,maxSum可以更新了。
代码
public int maxSubArray(int[] nums) {
int maxSum = Integer.MIN_VALUE;
int sum = 0;
for (int num : nums) {
if (sum < 0) {
sum = num;
} else {
sum += num;
}
if (sum > maxSum) {
maxSum = sum;
}
}
return maxSum;
}
可能下面的代码更好理解
class Solution {
public int maxSubArray(int[] nums) {
int max = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum = sum + nums[i];
if (sum > max) {
max = sum;
}
if (sum < 0) {
sum = 0;
}
}
return max;
}
}
不管怎么样,先直接往里加。判断下,是否比最大值大了,更新最大值。如果当前的sum小于0了,那么无论后面是正数还是负数,如果累加进来后肯定只会比后面的这个数小,不如只取后面的这个数。问题纠结点是,当前的和已经小于0了,是不是从当前的这个数再次开始呢?不要。因为,之所以这个sum会是小于0的,那么当前的这个数字肯定是小于0的,所以,sum直接从0开始就可以了。
然后再跟上次的最大值比较下
update 20220421
思路
dp[i]表示到下标i的最大和。那么他可以通过下面两种情况转变而来:
dp[i] = max(dp[i-1] + nums[i], nums[i])
代码
public int maxSubArray(int[] nums) {
// dp[i] = max(dp[i-1] + nums[i], nums[i])
int n = nums.length;
int[] dp = new int[n];
dp[0] = nums[0];
int max = dp[0];
for (int i = 1; i < n; i++) {
dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
max = Math.max(max, dp[i]);
}
return max;
}
public int maxSubArray(int[] nums) {
int sum = nums[0];
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
sum = Math.max(nums[i], sum+nums[i]);
max = Math.max(max, sum);
}
return max;
}
update20220513
代码更简洁
public int maxSubArray(int[] nums) {
int max = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
// 就等价于 sum = Math.max(sum + nums[i], nums[i])
sum = sum < 0 ? nums[i] : sum + nums[i];
max = Math.max(max, sum);
}
return max;
}
有没有发现跟动态规划的代码很像了,就是一模一样
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
update20220530 分治解法
- 根据mid分成两侧
- 左侧找最大的和
- 右侧找最大的和
- 还需要注意拼接的情况
- 测试用例:[-1,-9,2,3,4,6]
代码
public int maxSubArray(int[] nums) {
return maxSub(nums, 0, nums.length - 1);
}
private int maxSub(int[] nums, int left, int right) {
if (left == right) {
return nums[left];
}
int mid = left + (right - left) / 2;
int leftMax = maxSub(nums, left, mid);
int rightMax = maxSub(nums, mid + 1, right);
int crossMax = crossMax(nums, left, right);
return Math.max(crossMax, Math.max(leftMax, rightMax));
}
private int crossMax(int[] nums, int left, int right) {
int mid = left + (right - left) / 2;
int leftSum = nums[mid];
int leftMax = leftSum;
for (int i = mid - 1; i >= left; i--) {
leftSum += nums[i];
leftMax = Math.max(leftMax, leftSum);
}
int rightSum = nums[mid + 1];
int rightMax = rightSum;
for (int i = mid + 2; i <= right; i++) {
rightSum += nums[i];
rightMax = Math.max(rightMax, rightSum);
}
return leftMax + rightMax;
}
硬广告
欢迎关注公众号:double6