给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。

注意:
数组长度 n 满足以下条件:

1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)

示例:

输入:
nums = [7,2,5,10,8]
m = 2

输出:
18

解释:
一共有四种方法将nums分割为2个子数组。
其中最好的方式是将其分为[7,2,5] 和 [10,8],
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。

动态规划

看到「将数组分割为 m 段,求……」就知道动态规划该出场了。

令 dp[i][j] 表示将数组的前 i 个数分割为 j 段所能得到的最大连续子数组和的最小值。

考虑第 j 段的具体范围,枚举k,其中前 k 个数被分割为 j-1 段,而第 k+1 到第 i 个数为第 j 段。

这 j 段子数组中和的最大值,就等于 dp[k][j-1] 与 subSum(k+1,i) 中的较大值,其中 subSum(i,j) 表示数组 nums 中下标落在区间 [i,j] 内的数的和。

由于我们要使得子数组中和的最大值最小,因此可以列出如下的状态转移方程:

410. Split Array Largest Sum 分割数组的最大值_最小值

对于状态 dp[i][j],由于我们不能分出空的子数组,因此合法的状态必须有 i≥j。对于不合法(i < j)的状态,由于我们的目标是求出最小值,因此可以将这些状态全部初始化为一个很大的数。

Code

class Solution:
	def splitArray(self, nums: List[int], m: int) -> int:
		length, subSum = len(nums), [0]
		dp = [[sys.maxsize for _ in range(m + 1)] for _ in range(length + 1)]
		dp[0][0] = 0
		for element in nums:
			subSum.append(subSum[-1] + element)

		for i in range(1, length + 1):
			for j in range(1, min(i, m) + 1):
				for k in range(i):
					dp[i][j] = min(dp[i][j], max(dp[k][j - 1], subSum[i] - subSum[k]))
		return dp[length][m]

复杂度分析

时间复杂度:O(n2×m),其中 n 是数组的长度,m 是分成的非空的连续子数组的个数。总状态数为 O(n×m),状态转移时间复杂度 O(n),所以总时间复杂度为 O(n2×m)。

空间复杂度:O(n \times m)O(n×m),为动态规划数组的开销。