算法Day 2 (数组篇)

178 阅读2分钟

LeetCode #53 最大子序和

题目描述

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

示例

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

方法一 (暴力法)

/*
    遍历每个位置,找出每个位置的最大子序,并与当前最大子序比较
    时间复杂度O(n * n)
*/
var maxSubArray = function(nums) {
    var max = nums[0]; // 定义初始最大子序
    for(var i=0; i<nums.length; i++) {
        var sum = 0;
        for(var j=i; j<nums.length; j++) {
            sum += nums[j];
            // 求每个位置的子序和,将和与当前最大子序比较。
            if(sum > max) {
                max = sum;
            }
        }
    }
    return max;
}

方法二 (贪心法)

/*
    从起始位置开始累加,对于每个位置,只要累加值大于0,我就贪心!
    每个位置选择最佳方案,最终就是最佳方案
    并与当前最大子序比较,进行更新
*/
var maxSubArray = function(nums) {
    var max = nums[0];
    var curSum = nums[0];
    for(var i=1; i<nums.length; i++) {
        if(curSum > 0) {
            // 我的理解是对于当前位置来说,如果前面累加的最大值大于0,那么就拼接上
            curSum += nums[i];
        } else {
            // 否则,从当前位置重新开始计算最大子序
            curSum = nums[i];
        }
        // 更新最大子序
        max = Math.max(max, curSum);
    }
    return max;
}

方法三 (动态规划)


var maxSubArray = function(nums) {
    var max = nums[0];
    for(var i=1; i<nums.length; i++) {
        if(nums[i - 1] > 0) {
            // 计算到0-i位置的最大子序值
            nums[i] += nums[i-1];
        }
        max = Math.max(max, nums[i]);
    }
    return max;
}

方法四 (分治法)

/*
    把问题分割成一个个小问题,分别求解,然后把小问题的解组合起来,就是最终解。
    这里将数组对半切割,考虑最大子序的位置,可能在左半边数组,也可能在右边数组,也可以跨越左右两边(这时最大子序必然包括数组中间值)
*/
var maxSubArray = function(nums) {
    return maxSubArrayHelper(nums, 0, nums.length - 1);
}

/*
    注意左右数组的起始值,不能重复计算
*/
var maxSubCrossArray = function(nums, left, mid, right) {
    if(left === right) return nums[left];
    // 跨越两边时,由于中间值必然存在子序数组中,可以以中间值分半处理
    // 处理左半数组
    var sum = nums[mid];
    var leftMax = nums[mid]; 
    // 左半最大子序,因为位置固定了,求法类似暴力循环
    for(var i = mid - 1; i>=left; i--) {
        sum += nums[i]; // 累加
        if(sum > leftMax) {
            leftMax = sum;
        }
    }
    
    sum = nums[mid + 1];
    var rightMax = nums[mid + 1];
    for(var i = mid + 2; i<=right; i++) {
        sum += nums[i];
        if(sum > rightMax) {
            rightMax = sum;
        }
    }
    
    return leftMax + rightMax;
}
var maxSubArrayHelper = function(nums, left, right) {
    if(left === right) {
        return nums[left];
    }
    var mid = Math.floor((left + right) / 2); // 中间值
    var leftSum = maxSubArrayHelper(nums, left, mid); // 左数组子序
    var rightSum = maxSubArrayHelper(nums, mid + 1, right); // 右数组子序
    var midSum = maxSubCrossArray(nums, left, mid, right);
    
    return Math.max(leftSum, rightSum, midSum);
}