LeetCode Hot100(71/100)——152. 乘积最大子数组

文章目录

题目链接

LeetCode 152. 乘积最大子数组

题目说明

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

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

示例 1:

复制代码
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:

复制代码
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

提示:

  • 1 <= nums.length <= 2 × 10⁴
  • -10 <= nums[i] <= 10
  • nums 的任何前缀或后缀的乘积都保证是一个 32 位整数

问题分析

这道题的核心难点在于处理负数。与求和不同,乘积运算有以下特点:

  1. 负数乘以负数会变成正数
  2. 遇到 0 会使乘积归零
  3. 当前的最小值(负数)可能在下一步变成最大值

正数
负数



遍历数组
当前元素
最大值变更大

最小值变更小
最大值和最小值互换
重置为0
更新全局最大值
是否遍历完
返回结果

解法一:动态规划(双状态维护)

原理讲解

这是最优雅的解法。核心思想是同时维护两个状态:

  • maxProduct:以当前位置结尾的子数组的最大乘积
  • minProduct:以当前位置结尾的子数组的最小乘积

为什么要维护最小值?因为当遇到负数时,之前的最小值(可能是很大的负数)乘以当前负数会变成很大的正数。

状态转移方程:

复制代码
maxProduct = max(nums[i], maxProduct * nums[i], minProduct * nums[i])
minProduct = min(nums[i], maxProduct * nums[i], minProduct * nums[i])

当前元素
计算三个候选值
nums_i
maxProduct × nums_i
minProduct × nums_i
取最大值作为新maxProduct
取最小值作为新minProduct

Java实现

java 复制代码
class Solution {
    public int maxProduct(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        // 初始化最大值、最小值和结果
        int maxProduct = nums[0];
        int minProduct = nums[0];
        int result = nums[0];
        
        // 从第二个元素开始遍历
        for (int i = 1; i < nums.length; i++) {
            int current = nums[i];
            
            // 保存当前的maxProduct,因为计算minProduct时需要用到
            int tempMax = maxProduct;
            
            // 更新最大乘积:当前元素、当前元素×最大乘积、当前元素×最小乘积
            maxProduct = Math.max(current, Math.max(tempMax * current, minProduct * current));
            
            // 更新最小乘积
            minProduct = Math.min(current, Math.min(tempMax * current, minProduct * current));
            
            // 更新全局最大值
            result = Math.max(result, maxProduct);
        }
        
        return result;
    }
}

复杂度分析

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

解法二:动态规划(数组存储)

原理讲解

这个解法与解法一思路相同,但使用数组来存储每个位置的最大值和最小值,更直观地展示动态规划的过程。
dpMax数组
存储以i结尾的最大乘积
dpMin数组
存储以i结尾的最小乘积
状态转移
更新全局最大值

Java实现

java 复制代码
class Solution {
    public int maxProduct(int[] nums) {
        int n = nums.length;
        
        // dpMax[i] 表示以 nums[i] 结尾的子数组的最大乘积
        int[] dpMax = new int[n];
        // dpMin[i] 表示以 nums[i] 结尾的子数组的最小乘积
        int[] dpMin = new int[n];
        
        // 初始化
        dpMax[0] = nums[0];
        dpMin[0] = nums[0];
        int result = nums[0];
        
        // 状态转移
        for (int i = 1; i < n; i++) {
            dpMax[i] = Math.max(nums[i], Math.max(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i]));
            dpMin[i] = Math.min(nums[i], Math.min(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i]));
            result = Math.max(result, dpMax[i]);
        }
        
        return result;
    }
}

复杂度分析

  • 时间复杂度:O(n),遍历数组一次
  • 空间复杂度:O(n),需要两个长度为 n 的数组

解法三:双向遍历

原理讲解

这是一个巧妙的解法。观察发现,如果数组中没有 0,那么最大乘积子数组要么是整个数组,要么是去掉前面若干元素,要么是去掉后面若干元素。

核心思想:

  1. 从左到右遍历一次,计算累积乘积
  2. 从右到左遍历一次,计算累积乘积
  3. 遇到 0 时重置乘积为 1
  4. 记录过程中的最大值

右向左遍历 数组 左向右遍历 右向左遍历 数组 左向右遍历 遇到0重置为1 遇到0重置为1 返回两次遍历的最大值 计算前缀乘积 更新最大值 计算后缀乘积 更新最大值

Java实现

java 复制代码
class Solution {
    public int maxProduct(int[] nums) {
        int n = nums.length;
        int result = Integer.MIN_VALUE;
        
        // 从左到右遍历
        int product = 1;
        for (int i = 0; i < n; i++) {
            product *= nums[i];
            result = Math.max(result, product);
            // 遇到0重置
            if (product == 0) {
                product = 1;
            }
        }
        
        // 从右到左遍历
        product = 1;
        for (int i = n - 1; i >= 0; i--) {
            product *= nums[i];
            result = Math.max(result, product);
            // 遇到0重置
            if (product == 0) {
                product = 1;
            }
        }
        
        return result;
    }
}

复杂度分析

  • 时间复杂度:O(n),需要遍历数组两次
  • 空间复杂度:O(1),只使用常数个变量

测试用例演示

让我们用示例 [2,3,-2,4] 来演示解法一的执行过程:
初始: max=2, min=2, result=2
i=1, num=3
max=max_6,6,6_=6

min=min_3,6,6_=3

result=6
i=2, num=-2
max=max_-2,-12,-6_=-2

min=min_-2,-12,-6_=-12

result=6
i=3, num=4
max=max_4,-8,-48_=4

min=min_4,-8,-48_=-48

result=6
返回 6

解法对比分析

解法 时间复杂度 空间复杂度 实现难度 代码可读性 推荐指数
动态规划(双状态维护) O(n) O(1) 中等 ⭐⭐⭐⭐⭐
动态规划(数组存储) O(n) O(n) 简单 很高 ⭐⭐⭐⭐
双向遍历 O(n) O(1) 较难 中等 ⭐⭐⭐

推荐使用解法一(动态规划-双状态维护),因为它在时间和空间上都达到了最优,代码简洁且易于理解。解法二适合初学者理解动态规划的思想,解法三则是一个巧妙的技巧,适合面试时展示思维能力。

相关推荐
智者知已应修善业17 小时前
【51单片机按键调节占空比3位数码管显示】2023-8-24
c++·经验分享·笔记·算法·51单片机
.54818 小时前
## Sorting(排序算法)
python·算法·排序算法
wuweijianlove18 小时前
算法的平均复杂度建模与性能回归分析的技术7
算法·数据挖掘·回归
子琦啊18 小时前
【算法复习】字符串 | 两个底层直觉,吃透高频题
linux·运维·算法
code_pgf20 小时前
Octo 算法详解-开源通用机器人策略模型技术报告
算法·机器人·开源
嘻嘻哈哈樱桃20 小时前
牛客经典101题题解集--动态规划
java·数据结构·python·算法·职场和发展·动态规划
脱氧核糖核酸__20 小时前
LeetCode热题100——234.回文链表(两种解法)
c++·算法·leetcode·链表
IronMurphy20 小时前
【算法四十二】118. 杨辉三角 198. 打家劫舍
算法
电科一班林耿超20 小时前
第 16 课:动态规划专题(二)—— 子序列与子数组问题:面试最高频的 DP 题型
数据结构·算法·动态规划
生信研究猿21 小时前
leetcode 416. 分割等和子集
算法·leetcode·职场和发展