贪心的小包
1. 问题描述
小包非常喜欢甜点,他收到了一次性送来的个甜点,每个甜点都有一个对应的喜爱值。这还不够,他让快递小哥连续送来了次相同的个甜点,并将这些甜点首尾相接排成一排。
现在,小包面前有个排成一排的甜点。他希望从中选择一段连续的甜点,使得这段甜点的总喜爱值最大化。需要注意的是,尽管小包喜欢甜食,但有些甜点可能不合口味,导致其喜爱值为负数。此外,小包至少需要选择一个甜点。
2. 解题思路
2.1 问题分析
这是一个经典的“最大子数组和问题”的变形,问题要求我们从重复次的数组中选取连续的一段,使得总喜爱值最大化。
由于次甜点的排列是周期性的,我们可以将该问题转化为:
- 使用 动态规划(Kadane算法)找到单次甜点数组的最大子序和。
- 扩展到的情况,通过数学推导处理多个周期的优化。
2.2 算法思想与实现
-
Kadane算法:
使用动态规划思想,通过维护当前子数组的最大值pre和全局最大值ans,快速找到一次 NNN 个甜点中的最大子序和。 -
扩展到多个周期: 当时,只需计算单次甜点数组的最大子序和即可。
当时,需考虑跨越数组边界的子序列,结合数组前缀和与后缀和求解最大值。
代码实现:
以下为本问题的核心代码
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算法的时间复杂度为。
额外计算前缀和与后缀和的时间复杂度也是。
因此,总体时间复杂度为,与的值无关。
空间复杂度
只使用常数额外空间,空间复杂度为。
4. 收获与总结
通过本题,我学习到了:
- 动态规划在最大子序和问题中的应用,以及如何扩展到多次数组拼接的场景。
- 前缀和与后缀和技巧的使用,有助于处理跨周期的连续子数组问题。
- 数学推导在复杂问题中的重要性,特别是在优化大规模数据时,避免直接展开数组。