LeetCode 152. Maximum Product Subarray 解题报告

本文介绍了解决LeetCode 152题——最大子数组乘积的方法。通过动态规划记录最大值和最小值,考虑负数情况的影响,实现了O(n)时间复杂度和O(1)空间复杂度的高效算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LeetCode 152. Maximum Product Subarray 解题报告

题目描述

Find the contiguous subarray within an array (containing at least one number) which has the largest product.


示例

Example 1:
given the array [2,3,-2,4],
the contiguous subarray [2,3] has the largest product = 6.


注意事项

没有给出.


解题思路

我的思路1:

这道题跟以前做的一道求最大连续子序列和的问题类似,不同的是这道题求的是最大积。很自然能够想到用动态规划去解,设置一个数组dp,dp[i]表示以nums[i]结尾的子序列的最大积,初始状态:
dp[0] = nums[0];
状态转移方程就是:
dp[i] = max(dp[i - 1] * nums[i], nums[i])
这样就OK啦!
好吧,开玩笑的,如果真的是这样做,就完全掉入了这道题的陷阱里。求积里有个很重要的性质:负负得正。因此,如果有仔细观察题目的例子,会发现序列中会有负数的!所以,仅是记录最大值是不够的,比如序列[2, -1, -3],正确答案是1×3=3,然而用上面那个解法会得到2,原因就在于中间计算时可能会产生负值,但我们不能判断当前的负值会不会与后面的负值相乘得到更大的结果。

正确的解法应该是同时记录最大积和最小积,dp[i][0]表示以nums[i]结尾的子序列的最小积,dp[i][1]表示以nums[i]结尾的子序列的最大积。
初始状态:
dp[0][0] = nums[0];
dp[0][1] = nums[0];
由于可能存在负数,所以有三个数参与判断,状态转移方程:
dp[i][0] = min( min(dp[i - 1][0] * nums[i], dp[i - 1][1] * nums[i]), nums[i])
dp[i][1] = max( max(dp[i - 1][0] * nums[i], dp[i - 1][1] * nums[i]), nums[i])
可以在用一个变量result记录结果,每次计算出最大积时就更新一下result,最后返回result就行,见下面我的代码1,时间复杂度是O(n),空间复杂度是O(n)

优化思路:

通过状态转移方程可以看出计算dp[i][]时只需要用到dp[i - 1][],与dp[i - 2][]及前面的结果没有关系,因此空间复杂度可以进一步优化,只用两个变量minPro和maxPro存储前一个位置的最大积和最小积。
初始状态:
minPro = nums[0];
maxPro = nums[0];
状态转移方程:
minPro = min( min(minPro * nums[i], maxPro * nums[i]), nums[i])
maxPro = max( max(minPro * nums[i], maxPro * nums[i]), nums[i])
另外状态转移方程中重复做了两次乘法,所以可以用变量先算出结果再进行判断,见下文中我的代码2,时间复杂度是O(n),空间复杂度是O(1)


代码

我的代码1:

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size(), result = nums[0];
        // dp[i][0] min, dp[i][1] max
        vector<vector<int>> dp(n, vector<int>(2, nums[0]));

        dp[0][0] = nums[0];
        dp[0][1] = nums[0];

        for (int i = 1; i < n; i++) {
            dp[i][0] = min(min(dp[i - 1][0] * nums[i], dp[i - 1][1] * nums[i]), nums[i]);
            dp[i][1] = max(max(dp[i - 1][0] * nums[i], dp[i - 1][1] * nums[i]), nums[i]);

            if (dp[i][1] > result) {
                result = dp[i][1];
            }
        }

        return result;
    }
};

我的代码2:

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size(), result = nums[0];
        int minPro = nums[0], maxPro = nums[0];
        int temp0 = 0, temp1 = 0;

        for (int i = 1; i < n; i++) {
            temp0 = minPro * nums[i];
            temp1 = maxPro * nums[i];

            minPro = min(min(temp0, temp1), nums[i]);
            maxPro = max(max(temp0, temp1), nums[i]);

            result = max(maxPro, result);
        }

        return result;
    }
};

总结

这道题是一道比较经典的子序列变种体,关键是弄清楚结果的产生情况,既可能是由正数相乘得到,也有可能是由负数得到,所以必须存储最大积和最小积。
动归的题目通过之后可以想想能不能优化,一般是针对空间进行优化。因为我的能力比较差,所以一开始都是开最大的空间((⊙﹏⊙)b)。通过想想优化,能够更好地体会状态转移的过程,所以得养成这个习惯,勤能补拙,继续加油啦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值