问题描述
小包喜欢甜点,他一次性收到了 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 次,需要考虑以下情况:
- 仅在一个序列内选择连续甜点。
- 跨越两个序列(尾部+头部)的连续甜点。
- 跨越多个序列的连续甜点,可能包含多个完整的甜点序列。
关键思路与优化
-
预处理单序列信息:
- 最大子数组和 (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}): 计算单个序列的总喜爱值。
-
根据 MM 的值处理:
- 若 M=1M = 1,直接返回单序列的 max_subarray_sum\text{max_subarray_sum}。
- 若 M>1M > 1:
- 当 total_sum>0\text{total_sum} > 0:可以跨越多个序列,考虑以下三种可能:
- 单序列的最大子数组和。
- 跨越两个序列(后缀和 + 前缀和)。
- 跨越多个序列(后缀和 + 中间序列和 + 前缀和)。
- 当 total_sum≤0\text{total_sum} \leq 0:只能从单序列或跨越两个序列中选择最大值。
- 当 total_sum>0\text{total_sum} > 0:可以跨越多个序列,考虑以下三种可能:
-
边界处理:
- 所有甜点喜爱值都为负数时,返回其中最大的一个值。
- 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)
时间与空间复杂度分析
-
时间复杂度:
- 遍历数组计算 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)。
-
空间复杂度:
- 只使用了常量级变量存储中间结果,空间复杂度为 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
总结与反思
-
算法评价:
- 时间复杂度为 O(N),空间复杂度为 O(1)),能够高效处理大规模数据。
- 利用数学推导避免了实际构造超长数组的需求,适用于 M 非常大的场景。
-
问题回顾:
- 初期构造 N×MN \times M 的大数组导致内存溢出,后通过分析序列特性(前缀和、后缀和、总和)解决。
-
拓展思考:
- 该算法思想可推广到其他类似问题,如循环数组的最大子数组和、多段重复序列的处理等,具有较高的实用价值。