2025-09-05:长度至少为 M 的 K 个子数组之和。用go语言,给定一个整数数组 nums 和两个整数 k、m,要求在数组中选出 k 段互不重叠的连续区

0 阅读5分钟

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。

分步骤描述过程

  1. 前缀和数组构建

    • 首先,构建一个前缀和数组 s,其中 s[0] = 0s[i] = nums[0] + nums[1] + ... + nums[i-1](即 s[i] 表示前 i 个元素的和)。这样,任意子数组 nums[l..r] 的和可以通过 s[r+1] - s[l] 快速计算。
  2. 动态规划初始化

    • 定义两个数组 fg,长度为 n+1nnums 的长度),用于动态规划。f[j] 表示考虑前 j 个元素时,已经选取了若干段区间(具体段数由迭代决定)的最大和。
    • 初始时(即未选取任何区间),f 数组初始化为0或负无穷?但这里代码中未显式初始化,实际在迭代中会覆盖。
  3. 动态规划迭代(分段处理)

    • 外层循环从 i=1k,表示当前要选取第 i 段区间。
    • 对于每个 i,初始化 g[i*m-1] 为负无穷(因为至少需要 i*m 个元素才能放置 i 段,每段至少 m 个元素)。
    • 维护一个变量 mx,用于记录在当前位置之前(间隔 m 长度)的最大值(即 f[j-m] - s[j-m] 的最大值)。这个值的作用是帮助快速计算新一段区间的起始位置。
    • 内层循环从 j = i*mn - (k-i)*m(即保证剩余元素足够放置剩下的 k-i 段,每段至少 m 个元素):
      • 更新 mx:比较当前 mxf[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-mj)的和(即 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-mj)的和。但这里区间长度是固定的 m?实际上并不固定,因为 mx 是累积的,但这里每段至少 m 个,但可以多于 m?代码中通过 g[j] = max(g[j-1], mx+s[j]) 允许区间长度超过 m(因为 j 是递增的,mx 是动态更新的)。
    • 内层循环结束后,g 数组表示选取了 i 段区间(每段至少 m 个)且最后一段结束于位置 j 的最大和。
    • 然后交换 fgf 现在表示选取了 i 段区间后的最大和(以位置 j 结束),用于下一轮迭代(即选取 i+1 段)。
  4. 结果提取

    • 最终,f[n] 表示选取了 k 段区间(每段至少 m 个)且覆盖到整个数组末尾的最大和,即为所求。

总的时间复杂度和总的额外空间复杂度

  • 时间复杂度:外层循环 k 次,内层循环大约 O(n) 次(每次从 i*mn-(k-i)*m,总体是线性),因此总时间复杂度为 O(k * n)
  • 额外空间复杂度:使用了两个长度为 n+1 的数组 fg,因此额外空间复杂度为 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))  

在这里插入图片描述