2025-09-05:长度至少为 M 的 K 个子数组之和。用go语言,给定一个整数数组 nums 和两个整数 k、m,要求在数组中选出 k 段互不重叠的连续区间(每段至少包含 m 个元素),使这些区间中所有元素之和达到最大,返回该最大和。
1 <= nums.length <= 2000。
-10000 <= nums[i] <= 10000。
1 <= k <= floor(nums.length / m)。
1 <= m <= 3。
输入: nums = [1,2,-1,3,3,4], k = 2, m = 2。
输出: 13。
解释:
最优的选择是:
子数组 nums[3..5] 的和为 3 + 3 + 4 = 10(长度为 3 >= m)。
子数组 nums[0..1] 的和为 1 + 2 = 3(长度为 2 >= m)。
总和为 10 + 3 = 13。
题目来自力扣3473。
分步骤描述过程
-
前缀和数组构建:
- 首先,构建一个前缀和数组
s
,其中s[0] = 0
,s[i] = nums[0] + nums[1] + ... + nums[i-1]
(即s[i]
表示前i
个元素的和)。这样,任意子数组nums[l..r]
的和可以通过s[r+1] - s[l]
快速计算。
- 首先,构建一个前缀和数组
-
动态规划初始化:
- 定义两个数组
f
和g
,长度为n+1
(n
是nums
的长度),用于动态规划。f[j]
表示考虑前j
个元素时,已经选取了若干段区间(具体段数由迭代决定)的最大和。 - 初始时(即未选取任何区间),
f
数组初始化为0或负无穷?但这里代码中未显式初始化,实际在迭代中会覆盖。
- 定义两个数组
-
动态规划迭代(分段处理):
- 外层循环从
i=1
到k
,表示当前要选取第i
段区间。 - 对于每个
i
,初始化g[i*m-1]
为负无穷(因为至少需要i*m
个元素才能放置i
段,每段至少m
个元素)。 - 维护一个变量
mx
,用于记录在当前位置之前(间隔m
长度)的最大值(即f[j-m] - s[j-m]
的最大值)。这个值的作用是帮助快速计算新一段区间的起始位置。 - 内层循环从
j = i*m
到n - (k-i)*m
(即保证剩余元素足够放置剩下的k-i
段,每段至少m
个元素):- 更新
mx
:比较当前mx
和f[j-m] - s[j-m]
,取最大值。这里f[j-m] - s[j-m]
表示在位置j-m
结束的之前段的最大和减去前缀和(相当于为新区间预留位置)。 - 然后计算
g[j]
:有两种选择?但这里代码中直接取g[j-1]
和mx + s[j]
的最大值。mx + s[j]
的含义是:在j-m
位置结束的之前段的最大和(即f[j-m]
)加上当前区间(从j-m
到j
)的和(即s[j] - s[j-m]
)?实际上mx = max(..., f[j-m] - s[j-m])
,那么mx + s[j] = f[j-m] - s[j-m] + s[j] = f[j-m] + (s[j]-s[j-m])
,正好是前一段在j-m
结束的最大和加上当前段(长度为m
,从j-m
到j
)的和。但这里区间长度是固定的m
?实际上并不固定,因为mx
是累积的,但这里每段至少m
个,但可以多于m
?代码中通过g[j] = max(g[j-1], mx+s[j])
允许区间长度超过m
(因为j
是递增的,mx
是动态更新的)。
- 更新
- 内层循环结束后,
g
数组表示选取了i
段区间(每段至少m
个)且最后一段结束于位置j
的最大和。 - 然后交换
f
和g
:f
现在表示选取了i
段区间后的最大和(以位置j
结束),用于下一轮迭代(即选取i+1
段)。
- 外层循环从
-
结果提取:
- 最终,
f[n]
表示选取了k
段区间(每段至少m
个)且覆盖到整个数组末尾的最大和,即为所求。
- 最终,
总的时间复杂度和总的额外空间复杂度
- 时间复杂度:外层循环
k
次,内层循环大约O(n)
次(每次从i*m
到n-(k-i)*m
,总体是线性),因此总时间复杂度为 O(k * n)。 - 额外空间复杂度:使用了两个长度为
n+1
的数组f
和g
,因此额外空间复杂度为 O(n)。
注意:由于 m
很小(最多为3),且 k
最大为 floor(n/m)
(即大约 n/3
),所以实际运行效率较高。
Go完整代码如下:
package main
import (
"fmt"
"math"
)
func maxSum(nums []int, k, m int) int {
n := len(nums)
s := make([]int, n+1)
for i, x := range nums {
s[i+1] = s[i] + x
}
f := make([]int, n+1)
g := make([]int, n+1)
for i := 1; i <= k; i++ {
g[i*m-1] = math.MinInt
mx := math.MinInt
for j := i * m; j <= n-(k-i)*m; j++ {
mx = max(mx, f[j-m]-s[j-m])
g[j] = max(g[j-1], mx+s[j])
}
f, g = g, f
}
return f[n]
}
func main() {
nums := []int{1, 2, -1, 3, 3, 4}
k := 2
m := 2
result := maxSum(nums, k, m)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def max_sum(nums, k, m):
n = len(nums)
if n < k * m:
raise ValueError("数组长度不足以选出 k 个每个至少长度为 m 的子数组")
# 前缀和
s = [0] * (n + 1)
for i, x in enumerate(nums):
s[i + 1] = s[i] + x
NEG = -10**30
f = [0] * (n + 1)
g = [0] * (n + 1)
for i in range(1, k + 1):
# 初始化本轮 g(只需保证 g[start-1] 为 -inf)
for t in range(n + 1):
g[t] = 0
g[i * m - 1] = NEG
mx = NEG
start = i * m
end = n - (k - i) * m # inclusive
for j in range(start, end + 1):
# 更新候选最大值
val = f[j - m] - s[j - m]
if val > mx:
mx = val
# g[j] = max(g[j-1], mx + s[j])
prev = g[j - 1]
cur = mx + s[j]
g[j] = prev if prev > cur else cur
# 交换 f 和 g,为下一轮使用
f, g = g, f
return f[n]
if __name__ == "__main__":
nums = [1, 2, -1, 3, 3, 4]
k = 2
m = 2
print(max_sum(nums, k, m))