问题描述
小U在公司年会上运气极佳,赢得了一等奖。作为一等奖得主,他有机会在一排红包中做两次切割,将红包分成三部分,要求第一部分和第三部分的红包总金额相等。他可以获得的金额是第一部分红包的总金额。帮小U计算出他能从这些红包中拿到的最大奖金金额。
问题分析:
我们需要从一个红包金额的数组中选择两个切割点,将整个红包数组分成三个部分,并且要求:
- 第一部分和第三部分的金额相等。
- 我们的目标是让第一部分的金额尽可能大。
解题思路
1. 问题本质理解
假设数组 redpacks = [r1, r2, r3, ..., rn] 代表不同红包的金额。我们需要选定两个切割点,将这个数组划分为三个部分:
- 第一部分:从数组的第一个元素到第一个切割点之前的元素。
- 第二部分:从第一个切割点到第二个切割点之间的元素。
- 第三部分:从第二个切割点之后到数组的最后一个元素。
要求:第一部分和第三部分的总金额相等,并且最大化第一部分的金额。
举个简单的例子:对于数组 [1, 3, 4, 6, 7, 14],我们可以找到一种切割方式,使得第一部分和第三部分的金额相等,从而求出第一部分的最大金额。
2. 算法设计
我们可以通过以下步骤来解决这个问题:
-
前缀和的使用:
- 对于任意子数组
[i, j],其和可以通过前缀和数组prefix_sum计算得到。prefix_sum[i]表示数组redpacks[0]到redpacks[i-1]的元素和。 - 利用前缀和,可以在常数时间内计算任意区间的和。
- 对于任意子数组
-
遍历所有可能的切割点:
- 为了使得第一部分和第三部分的金额相等,我们需要枚举所有可能的切割点。设定
i为第一刀的位置,j为第二刀的位置。要保证i < j,并且每个部分至少有一个元素。 - 对于每一对切割点
(i, j),我们计算第一部分和第三部分的金额,并判断是否相等。若相等,则更新最大金额。
- 为了使得第一部分和第三部分的金额相等,我们需要枚举所有可能的切割点。设定
-
最大化第一部分金额:
- 在满足第一部分和第三部分相等的条件下,我们要选择第一部分的金额最大的一对切割点。
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)
代码逐步分析:
-
输入处理和前缀和计算:
n = len(redpacks) if n < 3: return 0- 代码首先判断数组
redpacks的长度n,如果n < 3,意味着无法进行有效的切割,因为至少需要三个部分才能分割出第一部分、第二部分和第三部分。因此直接返回0。
- 代码首先判断数组
-
初始化最大金额:
max_amount = 0 total = sum(redpacks)max_amount用于存储满足条件的最大第一部分金额。初始值为0。total计算红包数组的总金额,虽然在后续代码中没有直接使用,但它有助于理解后续部分的和计算。
-
前缀和数组的计算:
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]的和。
-
双重循环枚举切割点:
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]的和。
- 第一部分的金额:
-
-
检查条件并更新最大金额:
if first_part == third_part: max_amount = max(max_amount, first_part)- 如果第一部分和第三部分的金额相等,就更新最大金额
max_amount,选择其中较大的金额。
- 如果第一部分和第三部分的金额相等,就更新最大金额
-
返回结果:
return max_amount- 最后返回最大值
max_amount,即符合条件的最大第一部分金额。
- 最后返回最大值
4. 复杂度分析
时间复杂度:
- 计算前缀和的时间复杂度是 O(n)。
- 双重循环的时间复杂度是 O(n²),因为外层循环遍历
i从 0 到n-3,内层循环遍历j从i到n-2。 - 所以,总的时间复杂度是 O(n²)。
空间复杂度:
- 我们使用了一个
prefix_sum数组来存储前缀和,空间复杂度是 O(n)。