解题报告: 贪心的小包 | 豆包MarsCode AI刷题

47 阅读5分钟

问题描述

小包喜欢甜点,他一次性收到了 NN 个甜点,每个甜点都有一个喜爱值(可以为负数)。随后,小哥又连续送来了 MM 次相同的 NN 个甜点,将这些甜点排成一排,总共形成 N×MN \times M 个甜点。

小包希望从这排甜点中选择一段连续的甜点,使得选中甜点的总喜爱值最大化。需要注意,小包至少要选择一个甜点。


输入参数

  • 整数 NN:每次送来的甜点数量,1≤N≤1051 \leq N \leq 10^5。
  • 整数 MM:甜点送来的次数,1≤M≤1091 \leq M \leq 10^9。
  • 数组 data:长度为 NN,每个元素表示一个甜点的喜爱值,范围为 −106≤data[i]≤106-10^6 \leq \text{data}[i] \leq 10^6。

输出结果

  • 一个整数,表示在 N×MN \times M 个甜点中可以选择的连续甜点段的最大总喜爱值。

示例

示例 1:

输入:
N=5,M=1,data=[1,3,−9,2,4]N = 5, M = 1, \text{data} = [1, 3, -9, 2, 4]
输出:
66
解释:选择甜点 [2, 4],最大总喜爱值为 66。

示例 2:

输入:
N=5,M=3,data=[1,3,−9,2,4]N = 5, M = 3, \text{data} = [1, 3, -9, 2, 4]
输出:
1111
解释:排列后的甜点为 [1, 3, -9, 2, 4, 1, 3, -9, 2, 4, 1, 3, -9, 2, 4]
选择 [2, 4, 1, 3, -9, 2, 4, 1, 3] 这段连续甜点,最大总喜爱值为 1111。


解题思路

核心问题分析

  • 需要找到连续子数组的最大和。若 M=1M = 1,问题可直接转化为单个数组中的最大子数组和(Kadane 算法解决)。
  • 当 M>1M > 1 时,甜点序列会重复 MM 次,需要考虑以下情况:
    1. 仅在一个序列内选择连续甜点。
    2. 跨越两个序列(尾部+头部)的连续甜点。
    3. 跨越多个序列的连续甜点,可能包含多个完整的甜点序列。

关键思路与优化

  1. 预处理单序列信息:

    • 最大子数组和 (max_subarray_sum\text{max_subarray_sum}): 使用 Kadane 算法找到单个序列中连续子数组的最大和。
    • 最大前缀和 (max_prefix_sum\text{max_prefix_sum}): 从左到右计算到某点为止的最大累计和。
    • 最大后缀和 (max_suffix_sum\text{max_suffix_sum}): 从右到左计算到某点为止的最大累计和。
    • 单序列总和 (total_sum\text{total_sum}): 计算单个序列的总喜爱值。
  2. 根据 MM 的值处理:

    • 若 M=1M = 1,直接返回单序列的 max_subarray_sum\text{max_subarray_sum}。
    • 若 M>1M > 1:
      • 当 total_sum>0\text{total_sum} > 0:可以跨越多个序列,考虑以下三种可能:
        1. 单序列的最大子数组和。
        2. 跨越两个序列(后缀和 + 前缀和)。
        3. 跨越多个序列(后缀和 + 中间序列和 + 前缀和)。
      • 当 total_sum≤0\text{total_sum} \leq 0:只能从单序列或跨越两个序列中选择最大值。
  3. 边界处理:

    • 所有甜点喜爱值都为负数时,返回其中最大的一个值。
    • MM 非常大时,无需实际构造甜点数组,通过数学计算快速得出结果。

算法实现

以下是基于上述思路的 Python 代码实现:

def max_sweetness(N, M, data):
    # 单序列信息初始化
    total_sum = sum(data)
    max_subarray_sum = float('-inf')
    current_sum = 0

    max_prefix_sum = float('-inf')
    prefix_sum = 0

    max_suffix_sum = float('-inf')
    suffix_sum = 0

    # 遍历数组计算 max_subarray_sum, max_prefix_sum, max_suffix_sum
    for i in range(N):
        current_sum = max(data[i], current_sum + data[i])
        max_subarray_sum = max(max_subarray_sum, current_sum)

        prefix_sum += data[i]
        max_prefix_sum = max(max_prefix_sum, prefix_sum)

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

    # 根据 M 的值计算最终结果
    if M == 1:
        return max_subarray_sum
    else:
        if total_sum > 0:
            # 跨越多个序列
            option1 = max_subarray_sum
            option2 = max_suffix_sum + max_prefix_sum
            option3 = max_suffix_sum + max_prefix_sum + (M - 2) * total_sum
            return max(option1, option2, option3)
        else:
            # 不跨越完整序列
            option1 = max_subarray_sum
            option2 = max_suffix_sum + max_prefix_sum
            return max(option1, option2)

时间与空间复杂度分析

  1. 时间复杂度:

    • 遍历数组计算 max_subarray_sum\text{max_subarray_sum}、max_prefix_sum\text{max_prefix_sum} 和 max_suffix_sum\text{max_suffix_sum},复杂度为 O(N)O(N)。
    • 总体时间复杂度为 O(N)O(N)。
  2. 空间复杂度:

    • 只使用了常量级变量存储中间结果,空间复杂度为 O(1)O(1)。

测试样例与验证

# 测试样例
print(max_sweetness(5, 1, [1, 3, -9, 2, 4]))  # 输出: 6
print(max_sweetness(5, 3, [1, 3, -9, 2, 4]))  # 输出: 11
print(max_sweetness(5, 2, [-1, -2, -3, -4, -5]))  # 输出: -1
print(max_sweetness(3, 4, [5, -2, 3]))  # 输出: 20

总结与反思

  1. 算法评价:

    • 时间复杂度为 O(N),空间复杂度为 O(1)),能够高效处理大规模数据。
    • 利用数学推导避免了实际构造超长数组的需求,适用于 M 非常大的场景。
  2. 问题回顾:

    • 初期构造 N×MN \times M 的大数组导致内存溢出,后通过分析序列特性(前缀和、后缀和、总和)解决。
  3. 拓展思考:

    • 该算法思想可推广到其他类似问题,如循环数组的最大子数组和、多段重复序列的处理等,具有较高的实用价值。