【力扣100题】48.乘积最大子数组

题目描述

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

测试用例的答案是一个 32 位整数。注意,一个只包含一个元素的数组的乘积就是这个元素的值。

示例:

  • 输入:nums = [2,3,-2,4] → 输出:6(子数组 [2,3] 有最大乘积 6)
  • 输入:nums = [-2,0,-1] → 输出:0(结果不能为 2,因为 [-2,-1] 不是子数组)

解题思路总览

方法 思路 时间复杂度 空间复杂度
动态规划 同时维护最大乘积和最小乘积,因为负数会使最小变最大 O(n) O(1)
分治 递归处理每个区间,考虑奇数个负数和偶数个负数的情况 O(n log n) O(log n)
暴力枚举 枚举所有子数组计算乘积 O(n^2) O(1)

本题采用动态规划方法。


完整代码

cpp 复制代码
class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int ans = INT_MIN, imax = 1, imin = 1;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] < 0) {
                int temp = imax;
                imax = imin;
                imin = temp;
            }
            imax = max(imax * nums[i], nums[i]);
            imin = min(imin * nums[i], nums[i]);
            ans = max(ans, imax);
        }
        return ans;
    }
};

算法流程图

复制代码
输入: nums = [2, 3, -2, 4]

初始化:
  ans = INT_MIN = -∞
  imax = 1
  imin = 1

i = 0, nums[0] = 2:
  2 < 0? 否,不需要交换
  imax = max(1*2, 2) = max(2, 2) = 2
  imin = min(1*2, 2) = min(2, 2) = 2
  ans = max(-∞, 2) = 2

i = 1, nums[1] = 3:
  3 < 0? 否,不需要交换
  imax = max(2*3, 3) = max(6, 3) = 6
  imin = min(2*3, 3) = min(6, 3) = 3
  ans = max(2, 6) = 6

i = 2, nums[2] = -2:
  -2 < 0? 是,交换 imax 和 imin
    temp = imax = 6
    imax = imin = 3
    imin = temp = 6
  imax = max(3*(-2), -2) = max(-6, -2) = -2
  imin = min(6*(-2), -2) = min(-12, -2) = -12
  ans = max(6, -2) = 6

i = 3, nums[3] = 4:
  4 < 0? 否,不需要交换
  imax = max(-2*4, 4) = max(-8, 4) = 4
  imin = min(-12*4, 4) = min(-48, 4) = -48
  ans = max(6, 4) = 6

最终 ans = 6
输出: 6

逐行解析

cpp 复制代码
int ans = INT_MIN, imax = 1, imin = 1;

含义:

  • ans 记录全局最大乘积,初始化为最小整数
  • imax 记录以当前位置结尾的连续子数组的最大乘积
  • imin 记录以当前位置结尾的连续子数组的最小乘积
  • 初始化为 1 是因为乘积的起始值为 1(单位元)
cpp 复制代码
for (int i = 0; i < nums.size(); i++)

含义: 遍历数组,依次计算以每个位置结尾的子数组的最大乘积。

cpp 复制代码
if (nums[i] < 0) {
    int temp = imax;
    imax = imin;
    imin = temp;
}

含义: 如果当前元素是负数,会将最大乘积和最小乘积互换。因为负数乘以最大数会变成最小数,乘以最小数会变成最大数。

cpp 复制代码
imax = max(imax * nums[i], nums[i]);

含义: 更新 imax。取「之前累积的最大乘积乘以当前元素」和「重新从当前元素开始」中的较大值。

cpp 复制代码
imin = min(imin * nums[i], nums[i]);

含义: 更新 imin。取「之前累积的最小乘积乘以当前元素」和「重新从当前元素开始」中的较小值。

cpp 复制代码
ans = max(ans, imax);

含义: 更新全局最大乘积。

cpp 复制代码
return ans;

含义: 返回所有子数组中的最大乘积。


核心思想:为什么需要同时维护最大和最小乘积?

因为数组中可能存在负数

[2, 3, -2, 4] 为例:

  • 当遍历到 -2 时,之前累积的最大乘积是 6
  • 如果继续乘以负数 -26 × (-2) = -12,变成很小的数
  • 但如果我们之前累积的是最小乘积呢?2 × 3 × (-2) = -12,再乘以 -2 就变成 24 了!

所以,当遇到负数时,最大和最小会互换角色。必须同时记录最大和最小,才能应对各种情况。


复杂度分析

复杂度 说明
时间复杂度 O(n) 只需遍历一次数组
空间复杂度 O(1) 只使用了常数个变量

面试追问 FAQ

问题 答案
为什么初始化 imax = imin = 1 1 是乘法的单位元,方便统一处理「从当前位置重新开始」的情况
为什么不比较 imax * nums[i]1 * nums[i] 因为 nums[i] 本身就是 imax = 1imax * nums[i] 的一种情况
遇到 0 会怎样? 0 会让乘积变成 0,同时是子数组的分界线。max(0, imax*0) = 0,会重新从下一个位置开始计算
和最大子数组和(53题)有什么区别? 最大子数组和问题可以用贪心,但乘积问题因为负数的存在,必须用动态规划同时维护最大和最小
如何输出具体的子数组? 额外记录每个位置的 imax 是来自之前的累积还是重新开始,最后回溯得到起止位置
进阶:如何求乘积最小子数组? ans = max(ans, imax) 改为 ans = min(ans, imin) 即可

相关题目

题号 题目 难度 核心思路
152 乘积最大子数组 中等 动态规划(最大/最小乘积)
53 最大子数组和 中等 动态规划/贪心
918 环形子数组的最大和 中等 动态规划 + 环形数组
1567 乘积为正的最长子数组长度 中等 动态规划记录正/负乘积长度

总结

要点 内容
核心思想 同时维护最大乘积和最小乘积,因为负数会使两者互换
状态定义 imax[i] = 以第 i 个元素结尾的连续子数组的最大乘积
状态定义 imin[i] = 以第 i 个元素结尾的连续子数组的最小乘积
状态转移 遇到负数时交换 imax 和 imin,然后 imax = max(imax*nums[i], nums[i])
初始化 imax = imin = 1ans = INT_MIN
结果 返回遍历过程中的最大 ans

相关推荐
小小de风呀1 小时前
de风——【从零开始学C++】(七):string类详解
开发语言·c++·算法
YL200404261 小时前
042将有序数组转换为二叉搜索树
数据结构·算法·leetcode
qq_296553271 小时前
矩阵对角线遍历:从暴力到最优的优雅解法
数据结构·线性代数·算法·青少年编程·矩阵·深度优先遍历
洛水水1 小时前
【力扣100题】50.最长有效括号
算法·leetcode·职场和发展
数智工坊1 小时前
【BLIP论文阅读】:统一视觉语言理解与生成的自举式预训练范式
论文阅读·人工智能·深度学习·算法·transformer
yyy(十一月限定版)1 小时前
问题解决策略搜索训练3
算法
吃好睡好便好1 小时前
在Matlab中绘制圆锥三维曲面图
开发语言·人工智能·学习·算法·matlab·信息可视化
兰令水1 小时前
topcode【随机算法题】【2026.5.15打卡-java版本】
java·算法·leetcode
洛水水1 小时前
【力扣100题】44.完全平方数
算法·leetcode·职场和发展