LeetCode刷题挑战-javascript:53.最大子序和

151 阅读2分钟

R-C.jpeg

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

题目

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。  

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]

输出:6

解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]

输出:1

示例 3:

输入:nums = [0]

输出:0

示例 4:

输入:nums = [-1]

输出:-1

示例 5:

输入:nums = [-100000]

输出:-100000  

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

  进阶: 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

解题思路

方法一:暴力出奇迹

用两次for循环暴力列举出所有情况。

但是很不幸,在leetcode中提交会发现此中方法超时。

时间复杂度O(n^2)

/**
 * @param {number[]} nums
 * @return {number}
 */
const maxSubArray = (nums) => {
    let sum = 0, max = -Infinity;
    for(let i=0; i<nums.length; i++){
        sum = 0;
        for(let j=i; j<nums.length; j++){
            sum += nums[j];
            max = Math.max(max, sum);
        }
    }
    return max;
};

方法二:动态规划

  • 动态规划的是首先对数组进行遍历,当前最大连续子序列和为 sum,结果为 ans

  • 如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留并加上当前遍历数字

  • 如果 sum <= 0,则说明 sum 对结果无增益效果,需要舍弃,则 sum 直接更新为当前遍历数字

  • 每次比较 sum 和 ans的大小,将最大值置为ans,遍历结束返回结果

  • 时间复杂度:O(n)

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    let ans = nums[0];
    let sum = 0;
    for(const num of nums) {
        if(sum > 0) {
            sum += num;
        } else {
            sum = num;
        }
        ans = Math.max(ans, sum);
    }
    return ans;
};

方法三:分治法

分治法的做题思路是:先将问题分解为子问题;解决子问题后,再将子问题合并,解决主问题。

使用分治法解本题的思路是:

将数组分为 2 部分。例如 [1, 2, 3, 4] 被分为 [1, 2] 和 [3, 4] 通过递归计算,得到左右两部分的最大子序列和是 lsum,rsum 从数组中间开始向两边计算最大子序列和 cross 返回 max(lsum, cross, rsum) 整体过程可以参考来自 Leetcode 官方题解的图:

image.png

可以看到,分治法可行的关键的是:最大子序列和只可能出现在左子数组、右子数组或横跨左右子数组 这三种情况下。

代码实现如下:

/**
 * @param {number[]} nums
 * @param {number} left
 * @param {number} right
 * @param {number} mid
 * @return {number}
 */
function crossSum(nums, left, right, mid) {
    if (left === right) {
        return nums[left];
    }

    let leftMaxSum = Number.MIN_SAFE_INTEGER;
    let leftSum = 0;
    for (let i = mid; i >= left; --i) {
        leftSum += nums[i];
        leftMaxSum = Math.max(leftMaxSum, leftSum);
    }

    let rightMaxSum = Number.MIN_SAFE_INTEGER;
    let rightSum = 0;
    for (let i = mid + 1; i <= right; ++i) {
        rightSum += nums[i];
        rightMaxSum = Math.max(rightMaxSum, rightSum);
    }

    return leftMaxSum + rightMaxSum;
}

/**
 * @param {number[]} nums
 * @param {number} left
 * @param {number} right
 * @return {number}
 */
function __maxSubArray(nums, left, right) {
    if (left === right) {
        return nums[left];
    }

    const mid = Math.floor((left + right) / 2);
    const lsum = __maxSubArray(nums, left, mid);
    const rsum = __maxSubArray(nums, mid + 1, right);
    const cross = crossSum(nums, left, right, mid);

    return Math.max(lsum, rsum, cross);
}

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    return __maxSubArray(nums, 0, nums.length - 1);
};
  • 时间复杂度是O(NlogN)

  • 由于递归调用,所以空间复杂度是O(logN)

方法四:贪心法

在循环中找到不断找到当前最优的和 sum。

注意:sum 是 nums[i] 和 sum + nums[i]中最大的值。这种做法保证了 sum 是一直是针对连续数组算和。

代码实现如下:

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    let maxSum = (sum = nums[0]);
    for (let i = 1; i < nums.length; ++i) {
        sum = Math.max(nums[i], sum + nums[i]);
        maxSum = Math.max(maxSum, sum);
    }
    return maxSum;
};
  • 时间复杂度:O(1)

  • 空间复杂度:O(n)

结束语

只想感叹一句,大家说的最简单的一道动态规划,对于小葵来说真的好难啊 😭😭

硬着头皮看了好几遍题解才勉强看懂。

这里把最全的解题方法分享给大家。

这里是小葵🌻,只要把心朝着太阳的地方,就会有温暖~

让我们一起来攻克算法难关吧!!