在问 · 算法题解

152. 乘积最大子数组

中等动态规划动态规划(最大最小积)力扣原题 ↗

题目描述

输入一个整数数组 nums。在所有非空的连续子数组中,找出元素乘积最大的那一个,返回这个最大乘积。

子数组必须由相邻元素构成,至少含一个元素。只含一个元素时,乘积就是该元素本身。

数据保证答案在 32 位整数范围内。

示例 1
输入:nums = [2,3,-2,4]
输出:6

解释:连续的 [2,3] 乘积为 6,是最大值。

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

解释:-2 和 -1 不相邻,不能组成子数组,所以最大乘积是 [0] 给出的 0。

数据范围

  • 1 <= nums.length <= 2 * 10^4
  • -10 <= nums[i] <= 10
  • nums 任意子数组的乘积都在 32 位整数范围内。

解题思路:动态规划(最大最小积)

同时维护以当前位置结尾的最大积和最小积,因为负数会交换两者。

为什么正确

max_prod、min_prod 分别是当前前缀中以当前位置结尾的最大和最小乘积。

复杂度分析

时间 O(n),空间 O(1);按索引遍历,避免 nums[1:] 的 O(n) 切片。

易错点

遇到负数必须交换最大积和最小积,否则会丢掉负负得正的机会。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

from typing import List

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        max_prod = min_prod = answer = nums[0]
        for i in range(1, len(nums)):
            x = nums[i]
            if x < 0:
                max_prod, min_prod = min_prod, max_prod
            max_prod = max(x, max_prod * x)
            min_prod = min(x, min_prod * x)
            answer = max(answer, max_prod)
        return answer

载入编辑器,自己跑一遍 →

其他解法

两种方法都是 O(n) 时间、O(1) 空间;动态规划靠状态转移处理负数,前缀积与后缀积靠两次扫描覆盖所有候选子数组。

解法 2:前缀积与后缀积 时间 O(n),空间 O(1)

从左到右累乘得到前缀积,从右到左累乘得到后缀积,每一步都用当前积更新答案。累乘结果为 0 时,下一步从 1 重新开始。不含 0 的一段里,负数个数为偶数时整段最优;为奇数时,最优解一定是去掉最左或最右的那个负数之后的部分,正好被前缀积或后缀积覆盖。

不需要同时维护最大值和最小值,代码更短。正确性依赖对负数个数的分类讨论,不如动态规划容易推广到其他题。

from typing import List

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        n = len(nums)
        answer = nums[0]
        pre = suf = 0
        for i in range(n):
            pre = (pre or 1) * nums[i]
            suf = (suf or 1) * nums[n - 1 - i]
            answer = max(answer, pre, suf)
        return answer

动态规划 专题的其他题目

查看 动态规划 全部题目 →