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

89 阅读4分钟

贪心的小包题解

问题描述

小包非常喜欢吃甜点,他收到了一次性送来的 N 个甜点,每个甜点都有一个对应的喜爱值。小包要求小哥连续送来 M 次相同的 N 个甜点,并将这些甜点首尾相接排成一排。现在,小包面前有 (N × M) 个排成一排的甜点。小包希望从中选择一段连续的甜点,使得这段甜点的总喜爱值最大化。

然而,有些甜点可能不合口味,导致其喜爱值为负数。小包至少要选择一个甜点来满足他的贪心需求,因此任务是找到一段连续的甜点,使得这段甜点的总喜爱值最大化。

思路解析

这个问题可以通过求解最大子数组和问题来进行。我们有两种情况需要考虑:

  1. 单次数组的最大子数组和:如果 M = 1,则问题就简化为求给定数组 data 中的最大子数组和。

  2. 多次重复数组的最大子数组和:如果 M > 1,则问题变得更加复杂。由于数据是将原数组 data 重复了 M 次并首尾连接,我们不仅需要考虑单次数组中的最大子数组和,还要考虑通过重复数组中连接两部分的情况。主要是因为,跨越重复区间的子数组可能会有更大的和(之前有一次就错在没考虑这个问题)。

解题代码

def solution(N, M, data):
    def max_subarray_sum(arr):
        max_sum = current_sum = arr[0]
        for i in range(1, len(arr)):
            current_sum = max(arr[i], current_sum + arr[i])
            max_sum = max(max_sum, current_sum)
        return max_sum
    
    if M == 1:
        return max_subarray_sum(data)
    
    single_max_sum = max_subarray_sum(data)

    prefix_sum, suffix_sum = float('-inf'), float('-inf')
    current_prefix_sum, current_suffix_sum = 0, 0
    
    for i in range(N):
        current_prefix_sum += data[i]
        prefix_sum = max(prefix_sum, current_prefix_sum)
        
        current_suffix_sum += data[-1 - i]
        suffix_sum = max(suffix_sum, current_suffix_sum)

    total_sum = sum(data)
    
    # 当数组总和大于 0 时,考虑中间部分的贡献
    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)

if __name__ == "__main__":
    # Add your test cases here
    print(solution(5, 1, [1, 3, -9, 2, 4]) == 6)
    print(solution(5, 3, [1, 3, -9, 2, 4]) == 11)

解题思路详解

  1. 单次数组的最大子数组和

    对于单次送来的甜点,使用 Kadane 算法 来求解最大子数组和。Kadane 算法通过迭代遍历数组,维护当前的子数组和,若当前子数组和小于当前元素,则从当前元素重新开始计算子数组和。

  2. 前缀和和后缀和

    我们还需要考虑跨越多个重复区间的最大子数组和。例如,考虑选取从前缀部分开始,跨越到后缀部分的子数组。如果该跨越部分的和大于 0,那么中间部分的贡献就有意义。

    计算数组的前缀和和后缀和时,我们分别从数组的左边和右边开始计算最大子数组和,保留前缀和和后缀的最大值。

  3. 总和的作用

    当数组的总和大于 0 时,跨越多个区间的部分会有正贡献。因此,如果 M > 2,我们可以将中间部分的贡献计算为 (M - 2) * total_sum,并与前缀和和后缀和相加,得到跨越多个区间的最大子数组和。

  4. 结果计算

    最终结果是三者中的最大值:单次最大子数组和、前缀和加后缀和、考虑重复部分的和。

复杂度分析

  1. 时间复杂度

    • 计算单次数组的最大子数组和使用 Kadane 算法,时间复杂度为 O(N)
    • 计算前缀和和后缀和的最大值,分别需要遍历数组两次,时间复杂度也是 O(N)
    • 因此,总时间复杂度为 O(N),即与 N 成正比。
  2. 空间复杂度

    只需要一些常量空间来存储变量,如 prefix_sumsuffix_sum 等,因此空间复杂度为 O(1),除了输入数据外,不需要额外的空间。

感受与注意事项

边界情况

  • 如果 M = 1,直接计算单次数组的最大子数组和,结果较为简单。
  • 如果 M > 1,考虑重复部分的贡献时,需要特别注意数组总和的符号。如果总和为负,则跨区间的贡献会被忽略。