题目解析:最大相等分割红包金额 | 豆包MarsCode AI 刷题

93 阅读6分钟

问题描述

小U在公司年会上运气极佳,赢得了一等奖。作为一等奖得主,他有机会在一排红包中做两次切割,将红包分成三部分,要求第一部分和第三部分的红包总金额相等。他可以获得的金额是第一部分红包的总金额。帮小U计算出他能从这些红包中拿到的最大奖金金额。

问题分析:

我们需要从一个红包金额的数组中选择两个切割点,将整个红包数组分成三个部分,并且要求:

  • 第一部分和第三部分的金额相等。
  • 我们的目标是让第一部分的金额尽可能大。

解题思路

1. 问题本质理解

假设数组 redpacks = [r1, r2, r3, ..., rn] 代表不同红包的金额。我们需要选定两个切割点,将这个数组划分为三个部分:

  • 第一部分:从数组的第一个元素到第一个切割点之前的元素。
  • 第二部分:从第一个切割点到第二个切割点之间的元素。
  • 第三部分:从第二个切割点之后到数组的最后一个元素。

要求:第一部分和第三部分的总金额相等,并且最大化第一部分的金额。

举个简单的例子:对于数组 [1, 3, 4, 6, 7, 14],我们可以找到一种切割方式,使得第一部分和第三部分的金额相等,从而求出第一部分的最大金额。

2. 算法设计

我们可以通过以下步骤来解决这个问题:

  1. 前缀和的使用

    • 对于任意子数组 [i, j],其和可以通过前缀和数组 prefix_sum 计算得到。prefix_sum[i] 表示数组 redpacks[0] 到 redpacks[i-1] 的元素和。
    • 利用前缀和,可以在常数时间内计算任意区间的和。
  2. 遍历所有可能的切割点

    • 为了使得第一部分和第三部分的金额相等,我们需要枚举所有可能的切割点。设定 i 为第一刀的位置,j 为第二刀的位置。要保证 i < j,并且每个部分至少有一个元素。
    • 对于每一对切割点 (i, j),我们计算第一部分和第三部分的金额,并判断是否相等。若相等,则更新最大金额。
  3. 最大化第一部分金额

    • 在满足第一部分和第三部分相等的条件下,我们要选择第一部分的金额最大的一对切割点。

3. 具体实现分析

下面是代码的具体实现:

def solution(redpacks):
    n = len(redpacks)
    
    if n < 3:  # 如果红包数量少于3个,无法分割
        return 0
    
    max_amount = 0
    total = sum(redpacks)  # 计算总和
    
    # 计算前缀和,用于快速计算区间和
    prefix_sum = [0] * (n + 1)
    for i in range(n):
        prefix_sum[i + 1] = prefix_sum[i] + redpacks[i]
    
    # 尝试所有可能的切分位置
    # i 是第一刀的位置(切在i之后)
    # j 是第二刀的位置(切在j之后)
    for i in range(n-2):  # 留出至少两个位置给中间和最后部分
        for j in range(i, n-1):  # j必须在i之后,且要留出至少一个位置给最后部分
            first_part = prefix_sum[i+1]  # 从开始到第一刀
            third_part = prefix_sum[n] - prefix_sum[j+1]  # 从第二刀到结束
            
            # 如果第一部分等于第三部分,更新最大值
            if first_part == third_part:
                max_amount = max(max_amount, first_part)
    
    return max_amount
if __name__ == "__main__":
    #  You can add more test cases here
    print(solution([1, 3, 4, 6, 7, 14]) == 14)
    print(solution([10000]) == 0)
    print(solution([52, 13, 61, 64, 42, 26, 4, 27, 25]) == 52)
    print(solution([10 , 10 , 10 , 10]) == 20)
    print(solution([2, 5, 50, 30, 60, 52, 26, 5, 74, 83, 34, 96, 6, 88, 94, 80, 64, 22, 97, 47, 46, 25, 24, 43, 76, 24, 2, 42, 51, 96, 97, 87, 47, 93, 11, 98, 41, 54, 18, 16, 11, 96, 34, 36, 87, 24, 32, 27, 62, 72, 54, 14, 67, 5, 21, 20, 44, 55, 3, 82, 19, 45, 1, 52, 14, 44, 46, 39, 83, 27, 30, 87, 61, 56, 59, 10, 83, 80, 42, 44, 75, 39, 43, 41, 23, 93, 73, 50, 94, 94, 82, 46, 87, 60, 94, 47, 52, 67, 22, 50, 49, 8, 9, 30, 62, 87, 13, 11]) == 2627)

代码逐步分析:

  1. 输入处理和前缀和计算

    n = len(redpacks)
    if n < 3:
        return 0
    
    • 代码首先判断数组 redpacks 的长度 n,如果 n < 3,意味着无法进行有效的切割,因为至少需要三个部分才能分割出第一部分、第二部分和第三部分。因此直接返回 0
  2. 初始化最大金额

    max_amount = 0
    total = sum(redpacks)
    
    • max_amount 用于存储满足条件的最大第一部分金额。初始值为 0
    • total 计算红包数组的总金额,虽然在后续代码中没有直接使用,但它有助于理解后续部分的和计算。
  3. 前缀和数组的计算

    prefix_sum = [0] * (n + 1)
    for i in range(n):
        prefix_sum[i + 1] = prefix_sum[i] + redpacks[i]
    
    • prefix_sum[i] 存储数组 redpacks 从第 0 个元素到第 i-1 个元素的和(即前缀和)。例如,如果 redpacks = [1, 3, 4],那么 prefix_sum = [0, 1, 4, 8]。这样可以通过差值 prefix_sum[j+1] - prefix_sum[i] 来计算区间 [i, j] 的和。
  4. 双重循环枚举切割点

    for i in range(n-2):  # 留出至少两个位置给中间和最后部分
        for j in range(i, n-1):  # j必须在i之后,且要留出至少一个位置给最后部分
            first_part = prefix_sum[i+1]
            third_part = prefix_sum[n] - prefix_sum[j+1]
    
    • 外层循环 i 遍历可能的第一刀位置,保证至少留有两个位置给第三部分。

    • 内层循环 j 遍历可能的第二刀位置,保证至少留有一个位置给第三部分。

    • 对于每一对切割点 (i, j),我们计算:

      • 第一部分的金额:first_part = prefix_sum[i+1],即从 redpacks[0] 到 redpacks[i] 的和。
      • 第三部分的金额:third_part = prefix_sum[n] - prefix_sum[j+1],即从 redpacks[j+1] 到 redpacks[n-1] 的和。
  5. 检查条件并更新最大金额

    if first_part == third_part:
        max_amount = max(max_amount, first_part)
    
    • 如果第一部分和第三部分的金额相等,就更新最大金额 max_amount,选择其中较大的金额。
  6. 返回结果

    return max_amount
    
    • 最后返回最大值 max_amount,即符合条件的最大第一部分金额。

4. 复杂度分析

时间复杂度:

  • 计算前缀和的时间复杂度是 O(n)。
  • 双重循环的时间复杂度是 O(n²),因为外层循环遍历 i 从 0 到 n-3,内层循环遍历 j 从 i 到 n-2
  • 所以,总的时间复杂度是 O(n²)。

空间复杂度:

  • 我们使用了一个 prefix_sum 数组来存储前缀和,空间复杂度是 O(n)。