【LeetCode 热题 100】152. 乘积最大子数组——(解法一)递推

Problem: 152. 乘积最大子数组

文章目录

整体思路

这段代码旨在解决经典的 "乘积最大子数组" (Maximum Product Subarray) 问题。问题要求在一个包含正数、负数和零的整数数组中,找到一个连续子数组,使得该子数组内所有元素的乘积最大,并返回这个最大乘积。

与"最大子数组和"不同,乘积问题因为负数的存在而变得复杂:一个当前很小的负数(最小值)乘以另一个负数,可能会变成一个很大的正数(最大值)。因此,只维护最大值是不够的。

该算法采用了一种非常巧妙的 动态规划 方法。它在每一步都同时维护以当前元素结尾的最大乘积最小乘积

  1. 状态定义

    • 算法定义了两个DP数组:
      • dpMax[i]nums[i] 为结尾的连续子数组的最大乘积
      • dpMin[i]nums[i] 为结尾的连续子数组的最小乘积(这个主要是为了处理负数)。
  2. 状态转移方程

    • 为了计算 dpMax[i]dpMin[i],我们需要考虑 nums[i] 与前一个状态的关系。以 nums[i] 结尾的子数组,要么只包含 nums[i] 本身,要么是 nums[i] 连接在以 nums[i-1] 结尾的子数组后面。
    • nums[i] 与前面的子数组(dpMax[i-1]dpMin[i-1])相乘时,会出现以下情况:
      • 如果 nums[i] 是正数:dpMax[i-1] * nums[i] 可能是新的最大值,dpMin[i-1] * nums[i] 可能是新的最小值。
      • 如果 nums[i] 是负数:dpMin[i-1] * nums[i](负负得正)可能变成新的最大值,而 dpMax[i-1] * nums[i](正负得负)可能变成新的最小值。
    • 因此,dpMax[i] 的候选值有三个:
      1. dpMax[i-1] * nums[i]:前一个最大值乘以当前数。
      2. dpMin[i-1] * nums[i]:前一个最小值乘以当前数(处理负负得正)。
      3. nums[i]:不与前面连接,子数组只包含当前数自身。
        dpMax[i] 就是这三者中的最大值。
    • 同理,dpMin[i] 的候选值也是这三个,但取的是最小值。
    • 状态转移方程:
      dpMax[i] = max(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i], nums[i])
      dpMin[i] = min(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i], nums[i])
  3. 最终结果

    • dpMax[i] 仅代表以 nums[i] 结尾的最大乘积,不一定是全局的最大乘积。
    • 全局的最大乘积必然是所有 dpMax[i] 中的某一个。
    • 因此,在计算完整个 dpMax 数组后,需要遍历它来找到其中的最大值作为最终答案。

完整代码

java 复制代码
import java.util.Arrays;

class Solution {
    /**
     * 找到一个具有最大乘积的连续子数组,并返回其乘积。
     * @param nums 整数数组
     * @return 最大乘积
     */
    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];
        
        // 基础情况:以 nums[0] 结尾的子数组只有一个,其最大和最小乘积都是 nums[0]。
        dpMax[0] = dpMin[0] = nums[0];
        
        // 从第二个元素开始,应用状态转移方程
        for (int i = 1; i < n; i++) {
            int x = nums[i];
            
            // 计算 dpMax[i]:
            // 它的候选值有三个:
            // 1. dpMax[i-1] * x: 前一个最大值乘以当前数。
            // 2. dpMin[i-1] * x: 前一个最小值乘以当前数 (处理负负得正的情况)。
            // 3. x:             子数组只包含当前数自身。
            dpMax[i] = Math.max(Math.max(dpMax[i - 1] * x, dpMin[i - 1] * x), x);
            
            // 计算 dpMin[i],逻辑同上,只是取最小值。
            dpMin[i] = Math.min(Math.min(dpMax[i - 1] * x, dpMin[i - 1] * x), x);
        }
        
        // 全局最大乘积是所有 dpMax[i] 中的最大值。
        // 使用 stream API 来方便地找到数组中的最大值。
        return Arrays.stream(dpMax).max().getAsInt();
    }
}

时空复杂度

时间复杂度:O(N)

  1. 循环 :算法的主体是一个 for 循环,从 i=1 遍历到 n-1。算上初始化,整个 nums 数组被访问了一次。循环执行了 N-1 次。
  2. 循环内部操作
    • 在循环的每一次迭代中,执行的都是基本的乘法、Math.max/Math.min 和数组访问操作。这些操作的时间复杂度都是 O(1)
  3. 结果查找Arrays.stream(dpMax).max().getAsInt() 需要遍历整个 dpMax 数组一次来找到最大值。这部分的时间复杂度是 O(N)

综合分析

算法的总时间复杂度由两个独立的线性扫描组成:O(N) (填充DP数组) + O(N) (查找最大值)。因此,最终的时间复杂度是 O(N)

空间复杂度:O(N)

  1. 主要存储开销 :算法创建了两个名为 dpMaxdpMin 的整型数组来存储动态规划的所有中间状态。
  2. 空间大小 :每个数组的长度都与输入数组 nums 的长度 N 相同。因此,总的空间占用为 O(N) + O(N) = O(N)。

综合分析

算法所需的额外空间主要由 dpMaxdpMin 两个数组决定。因此,其空间复杂度为 O(N)

参考灵神

相关推荐
程序员水自流1 分钟前
【AI大模型第9集】Function Calling,让AI大模型连接外部世界
java·人工智能·llm
‿hhh4 分钟前
综合交通运行协调与应急指挥平台项目说明
java·ajax·npm·json·需求分析·个人开发·规格说明书
小徐Chao努力4 分钟前
【Langchain4j-Java AI开发】06-工具与函数调用
java·人工智能·python
无心水6 分钟前
【神经风格迁移:全链路压测】33、全链路监控与性能优化最佳实践:Java+Python+AI系统稳定性保障的终极武器
java·python·性能优化
萧曵 丶15 分钟前
Synchronized 详解及 JDK 版本优化
java·多线程·synchronized
夏幻灵30 分钟前
JAVA基础:基本数据类型和引用数据类型
java·开发语言
weixin1997010801636 分钟前
闲鱼 item_get - 商品详情接口对接全攻略:从入门到精通
java·后端·spring
水力魔方1 小时前
武理排水管网模拟分析系统应用专题5:模型克隆与并行计算
数据库·c++·算法·swmm
cike_y1 小时前
Spring-Bean的作用域&Bean的自动装配
java·开发语言·数据库·spring
qq_12498707531 小时前
基于深度学习的蘑菇种类识别系统的设计与实现(源码+论文+部署+安装)
java·大数据·人工智能·深度学习·cnn·cnn算法