贪心的小包
以豆包MarsCode AI 刷题(代码练习)题库中贪心的小包一题为例
题目分析
通过阅读题目可以知道:由于甜点的排列是循环的,可以理解为一个长度为 N * M 的数组。我们要找到这段数组中的连续子数组,使其总和最大化
那么可以将所给的数组重复 M 次进行拼接,这样我们的问题就变成了:在拼接完后的数组中寻找数组中的连续子数组,使其总和最大化
假设拼接后的数组为 all ,那么最简单的思路便是:遍历 all 数组,并依次计算从当前位置开始到数组末尾的累加过程中出现的最大值,并保存下来,返回即可
具体的代码实现:
def solution(N, M, data):
# 拼接所有送来的甜点
all = []
for _ in range(M):
all.extend(data)
# 甜点总数
l = len(all)
# 存储最大值
max_num = all[-1]
# 遍历数组
for i in range(l):
result = count_after_max(all, i)
if result > max_num:
max_num = result
# 返回最大值
return max_num
# 计算从i开始的最大值
def count_after_max(arr, i):
themax = arr[i]
count = 0
for i in arr[i:]:
count += i
if count > themax:
themax = count
return themax
思路优化
这样虽然可以成功计算出我们想要的结果,但是分析一下计算过程就会发现,有大量重复性的计算,拼接后的数组 all 里,越往后的元素会被反复加上很多次,我们必须想方法进行优化
观察拼接后数组的情况,既然有大量循环的结构,由于是连续的,那么意味着实际上只需要考虑前两次 data 数组的重复即可,即在 data * 2 上找到最大连续子数组和,并不需要重复拼接 M 次
- 这样可以避免直接构造一个过长的数组,提高效率
- 但此时我们需要考虑重复的次数带来的额外价值
具体的代码实现:
def solution(N, M, data):
# 数据数组的总和
sum_data = sum(data)
# 单个数据数组的最大子数组和
max_single = kadane(data)
# 数据重复两次的最大子数组和(覆盖循环情况)
max_double = kadane(data * 2)
# 如果sum_data是正数,那么可以多次使用完整的数据数组
if M == 1:
max_result = max_single
elif sum_data > 0:
max_result = max_double + (M - 2) * sum_data
else:
max_result = max_double
return max_result
# Kadane 算法
def kadane(arr):
# 初始化最大和为数组的第一个元素
max_sum = arr[0]
# 初始化当前和为数组的第一个元素
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
最后 max_result 返回值的确定计算的解释:
- 如果
M == 1,即数组只重复一次,那么最大结果就是单个数据数组的最大子数组和 - 如果
sum_data > 0,即数组的总和大于 0,那么最大结果是数据重复两次的最大子数组和加上(M - 2)个数据数组的总和 - 否则,最大结果是数据重复两次的最大子数组和
复杂度分析
- 时间复杂度: O(N) 使用 Kadane 算法对
data和data * 2进行遍历 - 空间复杂度: O(1),不需要额外的空间
这是一道经典的循环数组的最大子数组和问题。本题难度并不算很高,如果在一开始没有思路,也可以使用豆包MarsCode的AI刷题功能,让AI给出一点思路提示和引导,通过多次反复刷题以识别这类题型,从而提高自己的算法能力