给定一个非负整数数组和一个整数 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,在所有情况中最小。
一道很经典的DP问题, 不过所有的算法比赛中, 我们在第一时间总会尝试用 borced solution
DFS 搜索把n长度的数据划分为m段,超时, 不过这段dfs代码设计的还是十分巧妙的, 适合很多种对数据做搜索的解法。
class Solution {
private int ans;
private int n, m;
private void dfs(int[] nums, int i, int cntSubarrays, int curSum, int curMax) {
if (i == n && cntSubarrays == m) {
ans = Math.min(ans, curMax);
return;
}
if (i == n) {
return;
}
if (i > 0) {
dfs(nums, i + 1, cntSubarrays, curSum + nums[i], Math.max(curMax, curSum + nums[i]));
}
if (cntSubarrays < m) {
dfs(nums, i + 1, cntSubarrays + 1, nums[i], Math.max(curMax, nums[i]));
}
}
public int splitArray(int[] nums, int M) {
ans = Integer.MAX_VALUE;
n = nums.length;
m = M;
dfs(nums, 0, 0, 0, 0);
return ans;
}
}
看了官方答案, dp才是王道,二分搜索+贪心, 想法太绕了, 一般不太容易理解。
这个问题满足无后向性的特点。我们可以用动态规划来解决它。
无后向性的特点意味着,一旦当前状态确定了,它就不会被之后的状态影响。在这个问题里面,如果我们在将 nums[0..i]
分成 j
份时得到了当前最小的分割数组的最大值,不论后面的部分怎么分割这个值不会受到影响。
首先我们把 f[i][j]
定义为将 nums[0..i]
分成 j
份时能得到的最小的分割子数组最大值。
对于第 j
个子数组,它为数组中下标 k + 1
到 i
的这一段。因此,f[i][j]
可以从 max(f[k][j - 1]
, nums[k + 1] + ... + nums[i])
这个公式中得到。遍历所有可能的 k
,会得到 f[i][j]
的最小值。
整个算法那的最终答案为f[n][m]
,其中 n
为数组大小。
good solution
class Solution {
public int splitArray(int[] nums, int m) {
int n = nums.length;
int[][] f = new int[n + 1][m + 1];
int[] sub = new int[n + 1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
f[i][j] = Integer.MAX_VALUE;
}
}
for (int i = 0; i < n; i++) {
sub[i + 1] = sub[i] + nums[i];
}
f[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int k = 0; k < i; k++) {
f[i][j] = Math.min(f[i][j], Math.max(f[k][j - 1], sub[i] - sub[k]));
}
}
}
return f[n][m];
}
}