leetcode 152乘积最大子数组

一、完整代码(精简注释)

复制代码
class Solution {
public:
    int maxProduct(vector<int>& nums) {
        // 初始化当前最大、最小乘积及全局最大值
        long long maxfn = nums[0];
        long long minfn = nums[0];
        long long ans = nums[0];

        for (int i = 1; i < nums.size(); i++) {
            // 保存上一轮的最大、最小乘积
            long long mx = maxfn, mn = minfn;

            // 三种情况取最大乘积
            maxfn = max((long long)nums[i],
                        max(mx * nums[i], mn * nums[i]));

            // 三种情况取最小乘积
            minfn = min((long long)nums[i],
                        min(mx * nums[i], mn * nums[i]));

            // 更新全局最大乘积
            ans = max(maxfn, ans);
        }

        return (int)ans;
    }
};

二、动态规划五步总结

1. 确定 DP 状态

这道题使用动态规划的空间优化形式,只需要三个变量:

  • maxfn:以当前元素结尾的连续子数组的最大乘积。

  • minfn:以当前元素结尾的连续子数组的最小乘积。

  • ans:遍历过程中出现的全局最大乘积。

为什么需要维护最小乘积?

因为负数乘负数可能得到最大的正数,所以之前的最小乘积也可能产生当前的最大乘积。

2. 确定递推公式

对于每个 nums[i],考虑三种情况:

  1. 当前元素自己作为一个子数组:nums[i]

  2. 前一个最大乘积乘以当前元素:mx * nums[i]

  3. 前一个最小乘积乘以当前元素:mn * nums[i]

最大乘积:

复制代码
maxfn = max((long long)nums[i],
            max(mx * nums[i], mn * nums[i]));

最小乘积:

复制代码
minfn = min((long long)nums[i],
            min(mx * nums[i], mn * nums[i]));

注意: mx 和 mn 保存的是上一轮的状态,避免更新 maxfn 后影响 minfn 的计算。

3. 初始化

复制代码
long long maxfn = nums[0];
long long minfn = nums[0];
long long ans = nums[0];

只有第一个元素时,最大乘积、最小乘积和全局最大乘积都等于 nums[0]。

4. 确定遍历顺序

复制代码
for(int i = 1; i < nums.size(); i++)

从左往右遍历,因为当前位置的最大、最小乘积依赖前一个位置的状态。

5. 举例推导

假设:

复制代码
nums = [-2, 3, -4]
i numsi maxfn minfn ans
0 -2 -2 -2 -2
1 3 3 -6 3
2 -4 24 -12 24

当 i = 2 时:

复制代码
mx = 3;
mn = -6;
nums[i] = -4;

计算:

复制代码
maxfn = max(-4, max(-12, 24)); // 24
minfn = min(-4, min(-12, 24)); // -12
ans = max(24, 3);              // 24

最终返回 24。

三、复杂度分析

  • 时间复杂度: \(O(n)\),只需要遍历一次数组。

  • 空间复杂度: \(O(1)\),只使用固定数量的变量,不需要额外的 DP 数组。

四、核心易错点

  1. 为什么需要同时维护最大值和最小值?

负数会改变乘积的大小关系,之前的最小乘积乘以负数可能成为最大乘积。

  1. 为什么需要 mx 和 mn?

因为更新当前最大、最小乘积时,都必须使用上一轮的状态,不能使用已经更新的 maxfn。

  1. 为什么 ans 不能直接等于 maxfn?

因为最大乘积子数组不一定以最后一个元素结尾,所以需要记录遍历过程中的最大值:

复制代码
ans = max(ans, maxfn);
  1. 为什么使用 long long?

为了扩大中间乘积的表示范围,降低整数溢出的风险。但 long long 也有范围限制,并不能保证任意输入都不溢出。

最终记忆:

这道题的核心就是 维护两个状态(最大乘积、最小乘积),每次从三种情况中更新当前状态,再通过 ans 保存全局最大值。

普通 DP 需要 \(O(n)\) 空间,而这里每次只依赖上一轮状态,因此可以优化到 \(O(1)\) 空间。

相关推荐
旖旎夜光5 小时前
LeetCode 315: 算右侧小于当前元素的个数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
文丑颜不良啊5 小时前
LeetCode1401~1420题目解析
java·leetcode
Nuanyt5 小时前
LeetCode Hot 100 链表专题2 3 题 (Java)
java·笔记·算法·leetcode·链表
文丑颜不良啊6 小时前
LeetCode1341~1360题目解析
java·leetcode
海绵宝宝转agent6 小时前
LeetCodehot100题解
java·笔记·学习·算法·leetcode·深度优先
Pniubi8 小时前
力扣543二叉树的直径(深度优先搜索法(递归))
算法·leetcode·职场和发展
j7~1 天前
【穷举、深搜与回溯算法】一棵决策树讲透全排列与子集
数据结构·c++·算法·leetcode·回溯·递归
Shaoshing1 天前
LeetCode100 42.接雨水
java·算法·leetcode
文丑颜不良啊1 天前
LeetCode1261~1280题目解析
java·leetcode
Shaoshing1 天前
LeetCode100 11. 盛最多水的容器
算法·leetcode