问题描述: 小R手上有一个长度为 n 的数组,数组中的元素分别来自集合 [0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024],即每个元素都是 2 的幂次或 0。小R想从这个数组中选取一段连续的子数组,使得子数组的乘积最大。任务是找到这个子数组的起始位置 x 和结束位置 y(起始位置为 1,结束位置为 n)。 解题思路 这个问题类似于 "最大子数组乘积" 问题,考虑到可能存在 0 的情况,我们不能简单地计算每个子数组的乘积,而是要动态更新最大和最小乘积。具体思路如下:
维护最大和最小乘积:
对于每个元素,我们不仅要维护当前的最大乘积,还要维护当前的最小乘积,因为乘以负数或较小的数字可能会使得最小乘积变大,从而得到更大的乘积。 动态计算:
对于每个元素,计算当前最大乘积和最小乘积: current_max = max(arr[i], current_max * arr[i], current_min * arr[i]) current_min = min(arr[i], current_max * arr[i], current_min * arr[i]) 注意,如果当前元素是 0,则最大乘积和最小乘积都应重置为 1,并且下一段子数组从下一个非零元素开始。 更新答案:
每当更新最大乘积时,记录当前的区间 [temp_start, i],并检查是否需要更新答案。如果当前的乘积更大,更新答案;如果相同,选择 x 更小的区间,若 x 相同则选择 y 更小的区间。 def solution(n: int, arr: list[int]) -> list[int]: # 初始化变量,用于记录最大乘积及其对应的区间起点和终点 max_product = float('-inf') # 最大乘积 start_idx = 0 # 最大乘积区间的起始位置 end_idx = 0 # 最大乘积区间的结束位置
current_max = 1 # 当前的最大乘积
current_min = 1 # 当前的最小乘积
temp_start = 0 # 临时记录当前区间的起点
for i in range(n):
if arr[i] == 0: # 遇到0时,重置乘积,重新开始
current_max = 1
current_min = 1
temp_start = i + 1 # 区间重新从下一位开始
continue
# 计算包括当前元素的最大乘积和最小乘积
temp_max = current_max * arr[i]
temp_min = current_min * arr[i]
current_max = max(arr[i], temp_max, temp_min)
current_min = min(arr[i], temp_max, temp_min)
# 更新最大乘积以及对应的区间
if current_max > max_product:
max_product = current_max
start_idx = temp_start
end_idx = i
elif current_max == max_product:
# 如果乘积相同,优先选择起点更小的区间
if temp_start < start_idx or (temp_start == start_idx and i < end_idx):
start_idx = temp_start
end_idx = i
return [start_idx + 1, end_idx + 1] # 返回的是1-based的索引
if name == "main": # 测试用例 print(solution(5, [1, 2, 4, 0, 8]) == [1, 3]) # 最大乘积区间为 [1, 2, 4] print(solution(7, [1, 2, 4, 8, 0, 256, 0]) == [6, 6]) # 最大乘积区间为 [256] print(solution(6, [0, 0, 1, 2, 4, 8]) == [3, 6]) # 最大乘积区间为 [1, 2, 4, 8]
代码详解 max_product = float('-inf'):初始化最大乘积为负无穷,用来确保任何乘积都会比它大。
current_max 和 current_min:这两个变量分别表示当前的最大乘积和最小乘积,考虑到负数可能反转乘积的符号,因此需要同时维护这两个值。
循环遍历数组:
对每个元素,检查是否为 0,如果是,则重置乘积并更新区间起点。 计算包括当前元素的最大乘积和最小乘积:current_max = max(arr[i], current_max * arr[i], current_min * arr[i]),这样能确保即使遇到负数也能考虑最小乘积对最大乘积的影响。 更新最大乘积和区间:
每次更新 current_max 时,如果它大于 max_product,更新答案。若相等,则比较区间的起点和终点,确保选择符合要求的区间。 返回结果:返回的是1-based的区间索引,因此需要将 start_idx + 1 和 end_idx + 1 作为结果返回。 新知识点总结 动态维护最大和最小乘积:
这是解这类最大子数组乘积问题的关键,因为乘积可能会因为负数的出现而变小或者变大,维护两个值有助于处理这种情况。 处理零元素的特殊情况:
0 的存在会重置当前的乘积,避免了乘积为 0 的影响。因此,遇到 0 时需要重置当前区间的起始位置。 学习建议 加强对动态规划和贪心算法的理解:
这道题目利用了动态维护当前最大和最小值的技巧,非常适合理解动态规划在连续子问题中的应用。 逐步提高解题思维的复杂度:
通过多做类似的动态规划题目,逐步从简单的数组和子数组问题向更复杂的场景过渡。 高效学习方法 刷题计划:
制定每周的刷题计划,重点刷动态规划和滑动窗口类的题目。这些题目帮助理清解决问题时需要维护的状态。 利用错题进行针对性复习:
每次做错的题目要仔细分析,弄清楚自己理解的漏洞,并结合相关的题解进行复习,确保不再犯类似的错误。 结合 AI 刷题功能:
使用 AI 刷题功能时,可以先尝试自己做题,若遇到困难,可以参考 AI 提供的解法和思路。结合 AI 提供的分析,逐步提升自己的解题能力。 结合学习资源:
可以结合在线教程和刷题平台的视频讲解,帮助理解每道题目的核心思路。通过多角度的学习,加深对算法和数据结构的理解。