今天我们来聊聊一个经典的动态规划问题——最大子数组和(又称最大子段和)的进阶版本:K次串联后的最大子数组之和。这个问题在力扣上编号为1191,属于动态规划中比较有代表性的题目。
最大子数组和问题是动态规划入门必学的经典案例。给定一个整数数组,我们需要找到一个连续子数组,使得该子数组的和最大。比如数组[-2,1,-3,4,-1,2,1,-5,4]的最大子数组和是6,对应的子数组是[4,-1,2,1]。
而1191题是这个问题的扩展版本:给定一个数组,我们将它重复K次连接起来形成新数组,然后在新数组上求最大子数组和。例如数组[1,2]重复3次变成[1,2,1,2,1,2],此时最大子数组和是9(取整个数组)。
我们先回顾一下原始最大子数组和问题的解法。经典的Kadane算法可以在O(n)时间内解决这个问题:
python复制def maxSubArray(nums):
max_current = max_global = nums[0]
for num in nums[1:]:
max_current = max(num, max_current + num)
max_global = max(max_global, max_current)
return max_global
这个算法的核心思想是维护两个变量:
对于每个元素,我们决定是将其加入当前子数组,还是以它作为新子数组的起点。
对于K次串联的问题,最直观的想法是先把数组重复K次拼接起来,然后直接应用Kadane算法。比如:
python复制def maxSubArrayKConcatenation(nums, k):
extended = nums * k
return maxSubArray(extended)
这种方法在小K值时可行,但当K很大时(比如题目中K可达10^5),拼接后的数组会非常长,导致时间和空间复杂度都变为O(n*k),这显然不可接受。
我们需要找到不实际拼接数组就能计算最大子数组和的方法。通过分析,可以发现几个关键点:
如果原数组所有元素的和sum(nums) ≤ 0,那么重复多次不会增加最大子数组和。此时最大子数组要么在单个数组内部,要么跨越两个数组的连接处。
如果sum(nums) > 0,那么重复K次后,最大子数组可能会包含多个完整数组的拼接,再加上前后部分。
设原数组的最大前缀和、最大后缀和、最大子数组和分别为max_prefix、max_suffix、max_subarray,数组总和为total。
那么K次串联后的最大子数组和可能是以下三种情况中的最大值:
基于以上分析,我们可以这样实现:
计算原数组的:
如果K == 1,直接返回max_subarray
否则,考虑两种情况:
取这两种情况的最大值
python复制def maxSubArrayKConcatenation(nums, k):
def kadane(arr):
max_current = max_global = arr[0]
for num in arr[1:]:
max_current = max(num, max_current + num)
max_global = max(max_global, max_current)
return max_global
def max_prefix(arr):
max_sum = current = arr[0]
for num in arr[1:]:
current += num
max_sum = max(max_sum, current)
return max_sum
def max_suffix(arr):
max_sum = current = arr[-1]
for num in reversed(arr[:-1]):
current += num
max_sum = max(max_sum, current)
return max_sum
total = sum(nums)
max_sub = kadane(nums)
if k == 1:
return max_sub
max_pre = max_prefix(nums)
max_suf = max_suffix(nums)
if total > 0:
return max(max_sub, max_suf + max_pre + total * (k - 2))
else:
return max(max_sub, max_suf + max_pre)
除了输入数组外,我们只使用了常数空间:O(1)
需要特别注意以下几种边界情况:
这个问题在实际中有多种应用场景:
循环缓冲区处理:在音频/视频处理中,数据常常以循环缓冲区形式存在,类似于数组重复连接。
周期性数据分析:比如分析每天的数据时,可能需要考虑跨天的情况。
基因组序列分析:DNA序列可以看作是碱基的循环排列。
类似的变种问题还包括:
在实现这个算法时,容易犯的错误包括:
调试时可以:
虽然我们的算法已经是O(n)复杂度,但还可以做一些优化:
这个问题可以进一步扩展:
这些扩展问题都值得深入思考,可以帮助更好地理解动态规划的应用。