LeetCode 一题三解——53.MaximumSubarray

441 阅读1分钟

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战

53.MaximumSubarray

题目

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

A subarray is a contiguous part of an array.

Example 1:

Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

Example 2:

Input: nums = [1]
Output: 1

Example 3:

Input: nums = [5,4,-1,7,8]
Output: 23

Constraints:

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

Follow up: If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

题目大意

给定一个数组nums,求其子序列和的最大值

子序列指数组中连续的若干元素组成的序列

解题思路及代码

方法一:动态规划

f(i)为以元素nums[i]结尾的子序列的和的最大值

f(i)(i >= 1)的值应该是:f(i - 1) + nums[i]nums[i]两者中的较大值

动态规划转移方程:f(i) = max{f(i - 1) + nums[i], nums[i]}

特别的,f(0) = nums[0]

代码

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function (nums) {
  let f = new Array(nums.length).fill(0);
  f[0] = nums[0];
  let max = nums[0];
  for (let i = 1; i < nums.length; i++) {
    f[i] = Math.max(f[i - 1] + nums[i], nums[i]);
    max = Math.max(max, f[i]);
  }
  return max;
};

复杂度

时间:O(n),n 为数组长度

空间:O(n)

优化

考虑到f(i)只和f(i - 1)有关,所以我们不必保存每一个f,只需用变量pre保存前一个f即可

代码

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function (nums) {
  let pre = nums[0];
  let max = nums[0];
  for (let i = 1; i < nums.length; i++) {
    pre = Math.max(pre + nums[i], nums[i]);
    max = Math.max(max, pre);
  }
  return max;
};

复杂度

时间:O(n),n 为数组长度

空间:O(1)

方法二:前缀和

前缀和是一个数组的某项下标之前(包括此项元素)的所有数组元素的和。

preSum[i]表示数组前i项元素之和,特别地,规定preSum[0] = 0

那么对于数组第i项到第j项的连续元素组成的子序列的和就可以表示成:preSum[j] - preSum[i - 1]

所以以nums[i - 1](第i项)为最后一个元素的子序列的和最大为:preSum[i] - min{preSum[x]}(0 <= x <= i - 1),即preSum[i]减去前i - 1项中的最小前缀和

用上面这个思路,我们在遍历数组的过程中,用preMin来维护最小前缀和,用sum来表示当前项的前缀和

代码

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function (nums) {
  let preMin = 0;
  let max = nums[0];
  for (let i = 0, sum = 0; i < nums.length; i++) {
    sum += nums[i];
    const currentSum = sum - preMin;
    if (max < currentSum) {
      max = currentSum;
    }
    if (preMin > sum) {
      preMin = sum;
    }
  }
  return max;
};

复杂度

时间:O(n),n 为数组长度

空间:O(1)

方法三

如果数组a的如下子列拥有最大子列和:a_i, a_{i+1}, ... , a_{j - 1}, a_j

可得如下结论:\sum_{k=i}^{l}a_k\geq0, \quad \forall i\leq l \leq j

意思是a_i到a_l的和一定大于等于0,否则a_{l+1}到a_j的和大于a_i到a_j的和

根据这个事实,我们遍历数组,用sum(初始值为0)维护某一段子列的和,如果sum < 0,则该子列必不可能是最大子列的一部分,重置sum = 0

代码

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function (nums) {
  let sum = 0
  let max = nums[0]
  for (let i = 0; i < nums.length; i++) {
    sum += nums[i]
    if (max < sum) {
      max = sum
    }
    if (sum < 0) {
      sum = 0
    }
  }
  return max
};

复杂度

时间:O(n),n 为数组长度

空间:O(1)