遇到"K次串联后最大子数组和"这个问题时,很多人的第一反应可能是直接套用经典的Kadane算法。但当我真正开始编码时,才发现事情没那么简单。这道LeetCode 1191题看似是最大子数组和的变种,实则暗藏多个需要仔细处理的边界条件。
这个问题的核心是:给定一个整数数组和一个整数k,需要将这个数组重复k次连接起来,然后在新形成的数组中找到具有最大和的连续子数组。举个例子,数组[1,2]重复3次变成[1,2,1,2,1,2],其中最大子数组和是1+2+1+2+1+2=9。
最直观的解法当然是直接构造k次串联后的数组,然后应用标准的Kadane算法。这种方法在小数据量时可行,但当k很大时(比如题目中的k≤10^5),构造出来的数组长度可能达到10^10,这显然会超出内存限制和时间限制。
python复制def maxSubArray(nums, k):
# 暴力法(仅用于理解问题,实际不可行)
extended = nums * k
max_sum = current_sum = extended[0]
for num in extended[1:]:
current_sum = max(num, current_sum + num)
max_sum = max(max_sum, current_sum)
return max_sum
经过分析,我发现这个问题有几个关键性质可以利用:
基于这些观察,我们可以将问题分为几种情况处理:
首先我们需要计算几个关键量:
python复制def compute_basic_values(nums):
n = len(nums)
# 计算Kadane结果(单数组内部最大子数组和)
kadane = current = nums[0]
for num in nums[1:]:
current = max(num, current + num)
kadane = max(kadane, current)
# 计算最大前缀和
prefix = [0] * n
prefix[0] = nums[0]
for i in range(1, n):
prefix[i] = prefix[i-1] + nums[i]
max_prefix = max(prefix)
# 计算最大后缀和
suffix = [0] * n
suffix[-1] = nums[-1]
for i in range(n-2, -1, -1):
suffix[i] = suffix[i+1] + nums[i]
max_suffix = max(suffix)
total = sum(nums)
return kadane, max_prefix, max_suffix, total
根据计算得到的基本量,我们可以分情况处理:
python复制def kConcatenationMaxSum(nums, k):
MOD = 10**9 + 7
if not nums or k == 0:
return 0
kadane, max_prefix, max_suffix, total = compute_basic_values(nums)
if k == 1:
return max(kadane, 0) % MOD
if total <= 0:
# 最大子数组最多跨越两个副本
case1 = kadane
case2 = max_prefix + max_suffix
return max(case1, case2, 0) % MOD
else:
# 可以包含(k-2)个完整数组,加上前后部分
case1 = kadane
case2 = max_suffix + max_prefix
case3 = max_suffix + (k-2)*total + max_prefix
return max(case1, case2, case3, 0) % MOD
在实际编码中,我发现有几个边界条件需要特别注意:
重要提示:最终结果需要对10^9+7取模,这是题目要求,容易被忽略。
让我们分析一下这个优化算法的复杂度:
这个复杂度完全能够处理题目给出的约束条件(n≤10^5,k≤10^5)。
我们可以进一步优化空间复杂度,避免存储整个前缀和后缀数组:
python复制def kConcatenationMaxSum(nums, k):
MOD = 10**9 + 7
if not nums or k == 0:
return 0
n = len(nums)
# 计算Kadane、max_prefix、max_suffix、total
kadane = current = nums[0]
max_prefix = current_prefix = nums[0]
max_suffix = current_suffix = nums[-1]
total = sum(nums)
for i in range(1, n):
# Kadane
current = max(nums[i], current + nums[i])
kadane = max(kadane, current)
# 前缀和
current_prefix += nums[i]
max_prefix = max(max_prefix, current_prefix)
for i in range(n-2, -1, -1):
# 后缀和
current_suffix += nums[i]
max_suffix = max(max_suffix, current_suffix)
if k == 1:
return max(kadane, 0) % MOD
if total <= 0:
return max(kadane, max_prefix + max_suffix, 0) % MOD
else:
return max(kadane, max_suffix + max_prefix, max_suffix + (k-2)*total + max_prefix, 0) % MOD
这个版本的空间复杂度降到了O(1),只使用了常数个额外变量。
在实际解决这个问题时,我遇到了几个典型的陷阱:
负数处理:忘记处理全负数数组时应该返回0的情况
模运算时机:过早进行模运算可能导致比较出错
整数溢出:当k很大时,(k-2)*total可能溢出
k=1的特殊情况:容易与k>1的情况混淆处理
掌握了这个问题的解法后,可以尝试解决一些类似的变种问题:
这些变种问题都可以基于类似的动态规划思想来解决,只是状态转移方程会有所不同。
在解决这个问题的过程中,我总结了几个重要的经验:
这道题很好地展示了如何从暴力解法出发,通过观察问题特性,逐步优化到最优解的过程。这种分析思路在解决其他算法问题时也同样适用。