[路飞]最大子数组和

132 阅读2分钟

记录 1 道算法题

最大子数组和

53. 最大子数组和 - 力扣(LeetCode) (leetcode-cn.com)


要求:返回一个数组内最大的子数组的和,子数组是指数组中一段1个元素以上组成的数组,不能跳开,位置是连续的。[-2,1,-3,4,-1,2,1,-5,4],最大子数组和:6。

最简单的是贪心的做法,我们假设一旦以往的和比当前元素小的时候,继续计算已经不是最大的子数组和,这时候要重新开始计算子数组。并且是将之前子数组的元素都放弃,从当前元素开始计算。

    function maxSubArray(nums) {
        // pre 是当前子数组的和
        let pre = nums[0]
        // sum 是最大子数组的和
        let sum = pre
        
        for(let i = 1; i < nums.length; i++) {
            const n = nums[i]
            // 更新子数组的和
            pre = Math.max(pre + n, n)
            // 比较最大的子数组和
            sum = Math.max(pre, sum)
        }
        
        return sum
    }

另外一种做法是利用分治,只计算分治的区间的最大子数组和,保留最大的那个。为此需要几个辅助的值,1、左边界为子数组开头的最大和,2、右边界为子数组结尾的最大和,3、左边界到右边界的总和,4、区间中最大的子数组和。

通过分治,将左右的两种 4 个值进行合并,最后一直递归返回,就能得出最大的子数组和,其他3种是为了辅助计算区间中的最大子数组和而存在的。

    class State {
        constructor(lSum, rSum, mSum, tSum) {
            // 左边界为子数组开头的最大和
            this.lSum = lSum
            // 右边界为子数组结尾的最大和
            this.rSum = rSum
            // 区间中最大的子数组和
            this.mSum = mSum
            // 左边界到右边界的总和
            this.tSum = tSum
        }
    }

    function maxSubArray(nums) {
        return getSum(nums, 0, nums.length - 1).mSum
    }
    
    function getSum(nums, left, right) {
        // 只有一个数的时候,直接返回
        if (left === right) return new State(nums[left], nums[left], nums[left], nums[left])
        // 分治统计 4 个值
        const mid = Math.floor((left + right) / 2)
        const leftGroup = getSub(nums, left, mid)
        const rightGroup = getSub(nums, mid + 1, right)
        // 生成需要的 4 个值
        return mergeGroup(leftGroup, rightGroup)
    }
    
    function mergeGroup(leftGroup, rightGroup) {
        // 总和就是两个的总和相加
        const tSum = leftGroup.tSum + rightGroup.tSum
        // 左边界为开始的最大和需要看,左边的数组的 lSum 是不是占满整个左数组,如果是则要多考虑右数组的 lSum
        const lSum = Math.max(leftGroup.lSum, leftGroup.lSum + rightGroup.lSum)
        // 右边界为子数组结束位置的最大和同理
        const rSum = Math.max(rightGroup.rSum, rightGroup.rSum + leftGroup.lSum)
        // 最大的子数组和是左右的最大子数组和里面的其中一个,或者是由左边和右边的一部分组成,跨左右两个数组
        const mSum = Math.max(
            leftGroup.mSum,
            rightGroup.mSum,
            leftGroup.rSum + rightGroup.lSum
        )
        
        return new State(lSum, rSum, mSum, tSum)
    }