最大乘积区间问题
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 难点分析
- 如何高效地遍历所有可能的区间
- 如何优化区间最小值的计算
- 如何优化区间和的计算
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²)的复杂度:
- 使用前缀和数组在O(1)时间内求得任意区间和
- 在遍历区间时同时维护最小值,避免重复计算
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
代码分析
- 前缀和预处理
prefix_sum = [0]
for num in arr:
prefix_sum.append(prefix_sum[-1] + num)
- 时间复杂度:O(N)
- 使得任意区间求和的时间复杂度降为O(1)
- 区间最小值维护
min_val = arr[i]
for j in range(i, n):
min_val = min(min_val, arr[j])
- 从左到右扩展区间时,通过维护当前最小值避免重复计算
- 每个新数只需要与当前最小值比较一次
- 区间和计算
sum_val = prefix_sum[j + 1] - prefix_sum[i]
- 使用前缀和可以O(1)时间得到任意区间和
- prefix_sum[j + 1] - prefix_sum[i] 表示区间[i,j]的和
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. 相关题目
- 柱状图中最大的矩形(LeetCode 84)
- 接雨水(LeetCode 42)
这类问题的共同点是:
- 需要找到元素左右两侧第一个比它小/大的元素
- 可以使用单调栈优化时间复杂度
- 通常需要考虑边界情况的处理