“青训营X豆包MarsCode 技术训练营第一课 | 豆包MarsCode AI 刷题”

86 阅读5分钟

问题描述: 小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 提供的分析,逐步提升自己的解题能力。 结合学习资源:

可以结合在线教程和刷题平台的视频讲解,帮助理解每道题目的核心思路。通过多角度的学习,加深对算法和数据结构的理解。