学习方法与心得4 | 豆包MarsCode AI 刷题-动态规划专题

113 阅读3分钟

贪心的小包

1. 问题描述

小包非常喜欢甜点,他收到了一次性送来的NN个甜点,每个甜点都有一个对应的喜爱值。这还不够,他让快递小哥连续送来了MM次相同的NN个甜点,并将这些甜点首尾相接排成一排。

现在,小包面前有N×MN×M个排成一排的甜点。他希望从中选择一段连续的甜点,使得这段甜点的总喜爱值最大化。需要注意的是,尽管小包喜欢甜食,但有些甜点可能不合口味,导致其喜爱值为负数。此外,小包至少需要选择一个甜点。

2. 解题思路

2.1 问题分析

这是一个经典的“最大子数组和问题”的变形,问题要求我们从重复MM次的数组中选取连续的一段,使得总喜爱值最大化。

由于MM次甜点的排列是周期性的,我们可以将该问题转化为:

  1. 使用 动态规划(Kadane算法)找到单次甜点数组的最大子序和。
  2. 扩展到M>1M>1的情况,通过数学推导处理多个周期的优化。

2.2 算法思想与实现

  • Kadane算法
    使用动态规划思想,通过维护当前子数组的最大值 pre 和全局最大值 ans,快速找到一次 NNN 个甜点中的最大子序和。

  • 扩展到多个周期: 当M=1M=1时,只需计算单次甜点数组的最大子序和即可。
    M>1M>1时,需考虑跨越数组边界的子序列,结合数组前缀和与后缀和求解最大值。

代码实现

以下为本问题的核心代码

def solution(N, M, data):
    # 计算一次甜点的最大子序和
    def kadane(arr):
        max_ending_here = max_so_far = arr[0]
        for x in arr[1:]:
            max_ending_here = max(x, max_ending_here + x)
            max_so_far = max(max_so_far, max_ending_here)
        return max_so_far

    single_max_sum = kadane(data)
    total_sum = sum(data)

    if M == 1:
        return single_max_sum
    
    # 考虑前缀和与后缀和的最大值
    prefix_sum = suffix_sum = float('-inf')
    current_prefix = current_suffix = 0

    for i in range(N):
        current_prefix += data[i]
        prefix_sum = max(prefix_sum, current_prefix)

    for i in range(N - 1, -1, -1):
        current_suffix += data[i]
        suffix_sum = max(suffix_sum, current_suffix)

    if total_sum > 0:
        return max(single_max_sum, prefix_sum + suffix_sum + (M - 2) * total_sum)
    else:
        return max(single_max_sum, prefix_sum + suffix_sum)

3. 代码详解

  • Kadane算法实现
    计算单次甜点中的最大子序和,作为基础解法。

  • 前缀和与后缀和计算
    遍历数组,找到最大前缀和与后缀和,为跨周期的子序列计算做准备。

  • 总和判断

    • 若单次甜点总和为正,可结合中间部分的多次总和进行最大化。
    • 若总和为负,最大值只能由跨周期边界的子序列或单次子序列贡献

5. 算法性能分析

时间复杂度

Kadane算法的时间复杂度为O(N)O(N)
额外计算前缀和与后缀和的时间复杂度也是O(N)O(N)
因此,总体时间复杂度为O(N)O(N),与MM的值无关。

空间复杂度

只使用常数额外空间,空间复杂度为O(1)O(1)

4. 收获与总结

通过本题,我学习到了:

  1. 动态规划在最大子序和问题中的应用,以及如何扩展到多次数组拼接的场景。
  2. 前缀和与后缀和技巧的使用,有助于处理跨周期的连续子数组问题。
  3. 数学推导在复杂问题中的重要性,特别是在优化大规模数据时,避免直接展开数组。