LeetCode 👉 HOT 100 👉 乘积最大子数组 - 中等题

1,187 阅读3分钟

「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」。

题目

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数。

子数组 是数组的连续子序列。

示例1

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

输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例2

输入: nums = [-2,0,-1]

输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

思路

这道题和 最大子数组和 传送门 ☞ 很像,但是又有很大不同;

按照上一篇的思路一,可以得出下面时间复杂度为 O(n^2) 的解法:

    /**
     * @param {number[]} nums
     * @return {number}
     */
    var maxProduct = function(nums) {
        const n = nums.length;
        if(n == 1) return nums[0];
        let max = Number.MIN_SAFE_INTEGER;

        for(let i = 0; i < n; i++) {
            let tempAns = 1;
            for(let j = i; j < n; j++) {
                // 计算当前连续子数组的乘积
                tempAns *= nums[j];
                max = Math.max(max, tempAns);
            }
        }

        return max;
    };

按照思路二,计算以 nums[i] 为结尾的子数组最大乘积,存储为 dp[i];按照道理,dp[i] = Math.max(nums[i]* dp[i - 1], nums[i]);但是考虑下面的例子:

[5, 6, −3, 4, −3]

dp 应该为 [5, 30, −3, 4, −3],然后就会得到最大乘积为 30,但是很明显,最后答案应该为所有数字的乘积;问题出在 负负得正 没有考虑到。如果 nums[i] 为正数,当然希望 dp[i - 1] 也为正数,那么乘积就会变大;反之亦然,当 nums[i] 为负数时,希望 dp[i - 1] 为负数,且越小越好;

所以可以再维护一个数组 mindp,用于维护以 nums[i] 为结尾的最小乘积,那么此时 dp[i] 应该为三个数取最大值,mindp[i] 应该为三个数取最小值,这三个数分别为 nums[i], nums[i] * dp[i - 1], nums[i] * mindp[i - 1]

解法如下

    /**
     * @param {number[]} nums
     * @return {number}
     */
    var maxProduct = function(nums) {
        const n = nums.length;
        if(n == 1) return nums[0];
        let max = nums[0];

        const dp = new Array(n).fill(0);
        const mindp = new Array(n).fill(0);

        dp[0] = mindp[0] = nums[0]

        for(let i = 1; i < n; i++) {
            let temp1 = nums[i] * mindp[i - 1];
            let temp2 = nums[i] * dp[i - 1];

            mindp[i] = Math.min(temp1, temp2, nums[i]);
            dp[i] = Math.max(temp1, temp2, nums[i]);

            max = Math.max(max, dp[i]);
        }
        

        return max;
    };

思考: 仔细看for循环里面的计算,可以发现 dp[i]、mindp[i] 只和上一步的 dp[i - 1]、mindp[i - 1] 有关,dp[i - 2]、mindp[i - 2] ... 都不会再次使用到;那么其实没有必要使用数组去存储,定义两个变量就好了,这样可以减少内存空间的使用;

优化内存空间

    /**
     * @param {number[]} nums
     * @return {number}
     */
    var maxProduct = function(nums) {
        const n = nums.length;
        if(n == 1) return nums[0];
        let max = nums[0];

        let prevMin = nums[0];
        let prevMax = nums[0];

        for(let i = 1; i < n; i++) {
            let temp1 = nums[i] * prevMin;
            let temp2 = nums[i] * prevMax;

            prevMin = Math.min(temp1, temp2, nums[i]);
            prevMax = Math.max(temp1, temp2, nums[i]);

            max = Math.max(max, prevMax);
        }
        

        return max;
    };

小结

对于这种,后一个结果可以由前一个结果,经过简单计算得出的题目,一般可以用 动态规划 的思路去处理;在分析递推公式时,需要仔细的分情况讨论,才能得出正确的结果。

LeetCode 👉 HOT 100 👉 乘积最大子数组 - 中等题

合集:LeetCode 👉 HOT 100,有空就会更新,大家多多支持,点个赞👍

如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄