Leetcode: 乘积最大子数组

43 阅读4分钟

原题:leetcode.cn/problems/ma…

题目描述

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

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

解题思路

  1. 双状态动态规划
    • 同时维护当前最大值和最小值(考虑负负得正)
    • 最大值 = max(当前值, 前最大值×当前值, 前最小值×当前值)
    • 最小值 = min(当前值, 前最大值×当前值, 前最小值×当前值)
  2. 全局结果更新
    • 每步更新全局最大乘积
  3. 滚动变量优化
    • 仅需保存前一状态的最大最小值
  4. 边界处理
    • 空数组返回0
    • 单元素返回该元素
    • 处理包含0和负数的场景

关键洞察

  1. 负负得正特性
    • 负数可能使最小乘积逆转为最大值
    • 必须同时维护最大和最小值
  2. 状态转移依赖
    • 当前状态同时依赖前一状态的最大值和最小值
  3. 断点重置机制
    • 遇到0时乘积序列重置
    • 负数不中断序列(可能后续负负得正)
  4. 子数组连续性
    • 乘积子数组必须是连续序列
    • 与子序列问题本质不同

复杂度分析

指标说明
时间复杂度O(n):单次遍历数组
空间复杂度O(1):仅需常数空间存储状态
最优情况O(n):必须遍历整个数组
最坏情况O(n):同上

代码实现

  • 基础版本
function maxProduct(nums) {

  const n = nums.length;

  // 创建最大乘积和最小乘积数组
  const maxDP = new Array(n).fill(0);
  const minDP = new Array(n).fill(0);

  // 初始化第一个元素
  maxDP[0] = minDP[0] = nums[0];
  let max = nums[0];  // 全局最大值

  for (let i = 1; i < n; i++) {
    // 计算三种可能值:当前值、当前值×前最大值、当前值×前最小值
    const candidates = [
      nums[i],
      maxDP[i - 1] * nums[i],
      minDP[i - 1] * nums[i]
    ];

    // 更新当前最大和最小乘积
    maxDP[i] = Math.max(...candidates);
    minDP[i] = Math.min(...candidates);

    // 更新全局最大值
    max = Math.max(max, maxDP[i]);
  }
  return max;
}
  • JavaScript-空间优化
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxProduct = function (nums) {
    let maxF = nums[0], minF = nums[0], result = nums[0];

    for (let i = 1; i < nums.length; i++) {
        const cur = nums[i];
        const tempMax = Math.max(cur, maxF * cur, minF * cur);
        const tempMin = Math.min(cur, maxF * cur, minF * cur);

        maxF = tempMax;
        minF = tempMin;
        result = Math.max(result, maxF);
    }
    return result;
};
  • python-空间优化
def maxProduct(nums):
cur_max = nums[0]
cur_min = nums[0]
global_max = nums[0]

for i in range(1, len(nums)):
# 遇到负数时交换最大值和最小值
if nums[i] < 0:
    cur_max, cur_min = cur_min, cur_max

# 更新当前最大值和最小值
cur_max = max(nums[i], cur_max * nums[i])
cur_min = min(nums[i], cur_min * nums[i])

# 更新全局最大值
global_max = max(global_max, cur_max)

return global_max
  • rust-空间优化
pub fn max_product(nums: Vec) -> i32 {
    let mut cur_max = nums[0];
    let mut cur_min = nums[0];
    let mut global_max = nums[0];

    for &num in nums.iter().skip(1) {
        // 遇到负数时交换最大值和最小值
        if num < 0 {
            std::mem::swap(&mut cur_max, &mut cur_min);
        }

        // 更新当前最大值和最小值
        cur_max = i32::max(num, cur_max * num);
        cur_min = i32::min(num, cur_min * num);

        // 更新全局最大值
        global_max = i32::max(global_max, cur_max);
    }

    global_max
}
  • 变种:输出最大乘积子数组
const maxProductWithSubarray = (nums) => {
    if (nums.length === 0) return 0;

    let maxProd = nums[0], minProd = nums[0];
    let result = nums[0];
    // 记录子数组起止位置
    let start = 0, end = 0;
    let minStart = 0, maxStart = 0;

    for (let i = 1; i < nums.length; i++) {
        const prevMax = maxProd, prevMin = minProd;
        const prevMaxStart = maxStart;

        // 更新最大值及起点
        if (nums[i] >= Math.max(prevMax * nums[i], prevMin * nums[i])) {
            maxProd = nums[i];
            maxStart = i;
        } else if (prevMax * nums[i] >= prevMin * nums[i]) {
            maxProd = prevMax * nums[i];
        } else {
            maxProd = prevMin * nums[i];
            maxStart = minStart;
        }

        // 更新最小值及起点
        if (nums[i] <= Math.min(prevMax * nums[i], prevMin * nums[i])) {
            minProd = nums[i];
            minStart = i;
        } else if (prevMax * nums[i] <= prevMin * nums[i]) {
            minProd = prevMax * nums[i];
        } else {
            minProd = prevMin * nums[i];
        }

        // 更新全局结果
        if (maxProd > result) {
            result = maxProd;
            start = maxStart;
            end = i;
        }
    }

    return {
        product: result,
        subarray: nums.slice(start, end + 1)
    };
};

实际应用场景

  1. 信号处理:音频波形中查找最大能量区段`
  2. 金融分析:股票收益率连续乘积最大化(复利)`
  3. 图像识别:检测图像中最亮区域(像素值乘积)`
  4. 生物信息学:DNA序列中高表达基因片段识别`

相似题目