当前位置: 首页 > news >正文

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

Problem: 152. 乘积最大子数组

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N)
    • 空间复杂度:O(N)

整体思路

这段代码旨在解决经典的 “乘积最大子数组” (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 数组后,需要遍历它来找到其中的最大值作为最终答案。

完整代码

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)

参考灵神

http://www.dtcms.com/a/348370.html

相关文章:

  • Vue2+Vue3前端开发笔记合集
  • 九、redis 入门 之 数据库和缓存一致性问题
  • Vue2+Vue3前端开发_Day12-Day14_大事件管理系统
  • Python无穷大与NaN处理完全指南:从基础到工程级解决方案
  • 【Java】springboot的自动配置
  • Wagtail CRX 简介
  • Python使用-Python环境安装
  • 【分布式中间件】Kafka 核心配置深度解析与优化指南
  • 【存在重复元素II】
  • 57 C++ 现代C++编程艺术6-类的内部类
  • MSF基础知识
  • Flask蓝图:模块化开发的利器
  • 数学建模--模糊综合评价法
  • 优化OpenHarmony中lspci命令实现直接获取设备具体型号
  • 7.6 残差网络
  • Palantir Foundry 领先其他数据平台5到10年:一位使用者的深入观察
  • vscode配置remote-ssh进行容器内开发
  • BQTLOCK 勒索软件即服务出现,拥有复杂的规避策略
  • MRO and mixin in Python Django
  • GD32VW553-IOT 测评和vscode开发环境搭建
  • Flutter性能优化完全指南:构建流畅应用的实用策略
  • 多奥将梯控系统、无线网桥及工业交换机的核心功能与参数整合为结构化表格,并补充应用价值分析
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(十八) 使用表格
  • 时间复杂度
  • 多核多线程应用程序开发可见性和乱序如何处理
  • ESNP LAB 笔记:配置MPLS(Part2)
  • Java Stream API详解
  • iptables 防火墙核心知识梳理(附实操指南)
  • VS2022的MFC中关联使用控制台并用printf输出调试信息
  • GPT 模型详解:从原理到应用