447最大乘积区间题解 | 豆包MarsCode AI刷题

65 阅读4分钟

最大乘积区间问题

1.1 问题分析

给定一个数组,求其所有子数组中"最小值乘以区间和"的最大值。

例如,对于数组 [6, 2, 1]

  • 区间 [6]: 最小值=6, 和=6, 结果=36
  • 区间 [2]: 最小值=2, 和=2, 结果=4
  • 区间 [1]: 最小值=1, 和=1, 结果=1
  • 区间 [6,2]: 最小值=2, 和=8, 结果=16
  • 区间 [2,1]: 最小值=1, 和=3, 结果=3
  • 区间 [6,2,1]: 最小值=1, 和=9, 结果=9

所有区间中最大值为36。

1.2 难点分析

  1. 如何高效地遍历所有可能的区间
  2. 如何优化区间最小值的计算
  3. 如何优化区间和的计算

2. 解题思路

2.1 暴力解法

最直观的解法是枚举所有可能的区间,计算每个区间的最小值和区间和,然后取最大值。

 def bruteforce(arr):
     n = len(arr)
     max_value = float('-inf')
     for i in range(n):
         for j in range(i, n):
             min_val = min(arr[i:j+1])
             sum_val = sum(arr[i:j+1])
             max_value = max(max_value, min_val * sum_val)
     return max_value

时间复杂度:O(n³),因为每个区间还要O(n)的时间计算最小值和区间和。

2.2 简单优化

我们可以通过以下优化使暴力解法达到O(N²)的复杂度:

  1. 使用前缀和数组在O(1)时间内求得任意区间和
  2. 在遍历区间时同时维护最小值,避免重复计算
 def optimized_bruteforce(arr):
     n = len(arr)
     # 预处理前缀和
     prefix_sum = [0]
     for num in arr:
         prefix_sum.append(prefix_sum[-1] + num)
         
     max_value = float('-inf')
     # 枚举区间起点
     for i in range(n):
         min_val = arr[i]  # 当前区间的最小值
         # 枚举区间终点
         for j in range(i, n):
             # 更新当前区间最小值
             min_val = min(min_val, arr[j])
             # 计算区间和 O(1)
             sum_val = prefix_sum[j + 1] - prefix_sum[i]
             # 更新结果
             max_value = max(max_value, min_val * sum_val)
     
     return max_value
代码分析
  1. 前缀和预处理
 prefix_sum = [0]
 for num in arr:
     prefix_sum.append(prefix_sum[-1] + num)
  • 时间复杂度:O(N)
  • 使得任意区间求和的时间复杂度降为O(1)
  1. 区间最小值维护
 min_val = arr[i]
 for j in range(i, n):
     min_val = min(min_val, arr[j])
  • 从左到右扩展区间时,通过维护当前最小值避免重复计算
  • 每个新数只需要与当前最小值比较一次
  1. 区间和计算
 sum_val = prefix_sum[j + 1] - prefix_sum[i]
  • 使用前缀和可以O(1)时间得到任意区间和
  • prefix_sum[j + 1] - prefix_sum[i] 表示区间[i,j]的和

2.3 优化解法 - 单调栈

关键思路:

  1. 对于每个数字,找到以它为最小值的最大区间
  2. 使用单调递增栈维护可能的最小值位置
  3. 使用前缀和数组O(1)时间求区间和
 def solution(n: int, a: list) -> int:
     # 构建前缀和数组
     sum_prefix = [0]
     for num in a:
         sum_prefix.append(sum_prefix[-1] + num)
     
     max_result = float('-inf')
     stack = []  # 单调递增栈
     
     # i表示当前处理的位置,遍历到n是为了处理栈中剩余元素
     for i in range(n + 1):
         # 当栈不为空,且当前数小于栈顶元素时,说明找到了栈顶元素作为最小值的右边界
         while stack and (i == n or a[stack[-1]] >= a[i]):
             min_idx = stack.pop()  # 当前最小值的位置
             # 左边界:栈中前一个元素的后一个位置,如果栈为空则为0
             left = stack[-1] + 1 if stack else 0
             # 右边界:当前位置-1
             right = i - 1
             # 计算区间和并更新结果
             interval_sum = sum_prefix[right + 1] - sum_prefix[left]
             max_result = max(max_result, a[min_idx] * interval_sum)
         
         if i < n:
             stack.append(i)
     
     return max_result

3. 代码解析

3.1 前缀和数组

 sum_prefix = [0]
 for num in a:
     sum_prefix.append(sum_prefix[-1] + num)
  • 用于O(1)时间获取任意区间和
  • sum_prefix[i]表示前i个数的和
  • 区间[i,j]的和 = sum_prefix[j+1] - sum_prefix[i]

3.2 单调栈的维护

 while stack and (i == n or a[stack[-1]] >= a[i]):
     min_idx = stack.pop()
  • 栈中存储的是递增序列的下标
  • 当遇到较小的数时,说明栈顶元素无法再向右扩展
  • i == n的情况用于处理栈中剩余元素

3.3 区间计算

 left = stack[-1] + 1 if stack else 0
 right = i - 1
 interval_sum = sum_prefix[right + 1] - sum_prefix[left]
  • left: 上一个较小值的后一个位置
  • right: 当前位置的前一个位置
  • 使用前缀和计算区间[left, right]的和

4. 复杂度分析

时间复杂度:O(n)

  • 构建前缀和数组:O(n)
  • 每个元素最多入栈出栈各一次:O(n)
  • 所有操作总计:O(n)

空间复杂度:O(n)

  • 前缀和数组:O(n)
  • 单调栈:O(n)

5. 相关题目

  1. 柱状图中最大的矩形(LeetCode 84)
  2. 接雨水(LeetCode 42)

这类问题的共同点是:

  • 需要找到元素左右两侧第一个比它小/大的元素
  • 可以使用单调栈优化时间复杂度
  • 通常需要考虑边界情况的处理