53. 最大子序和[简单]

87 阅读1分钟

问题

给定一个整数数组 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