给定一个非负整数数组和一个整数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,在所有情况中最小。
方法1:动态规划
分析:该问题中涉及到两个参数m, n;所以最大的状态空间数应该为m*n;
这里定义dp[i][j]: 表示将数组的前i个数分割为j段所能表达的最大连续子数组和的最小值;
状态转移: 考虑第j段的具体范围,枚举k, 使得前k个数被分割为j-1段,第k+1到第i个数为第j段。
此时,第j段的子数组和的最大值等于max(dp[k][j-1], sum(k+1, i))。sum(t1, t2)表示数组下标[t1, t2]内的数和。
结果:要使得子数组中和的最大值最小,可以列出如下的状态转移方程:
dpi][j] = min{max(dp[k][j-1], sum(k+1, i))} (for k = 0 to i-1)
对于状态f[i][j], 不能分出空的子数组,合法的状态有i >= j;对于不合法状态(i
int splitArray(vector<int> &nums, int m){
int n = nums.size();
typedef long long LL;
vector<vector<LL>> dp(n+1, vector<LL>(m+1, LLONG_MAX));
vector<LL> prefixSum(n+1, 0);// prefix_sum used for calc sum(i, j) by prefixSum[j] - prefixSum[i]
for(int i=0; i<n; ++i)
prefixSum[i+1] = prefixSum[i] + nums[i];
dp[0][0] = 0;
for(int i=1; i<=n; ++i){
for(int j=1; j<=m; ++j){
for(int k=0; k<i; ++k){
dp[i][j] = min(dp[i][j], max(dp[k][j-1], prefixSum[i] - prefixSum[k]));
}
}
}
return (int)dp[n][m]
}
方法2:二分查找(变形)和贪婪算法
解题思路:根据题目的描述可以知道,子数组的最大值是有范围的,在区间[max(nums), sum(nums)]之间。
假设:low = max(nums), high = sum(nums), mid = (low + high) / 2;此时可以计算数组和最大值不大于mid对应子数组个数cnt。
如果:cnt > m, 说明划分的子数组多了,也就是mid设置的偏小;low = mid + 1;
如果: cnt < m; 说明划分的子数组少了,即mid设置的偏大(或则正好是目标值), 更新high = mid;
int splitArray(vector<int>& nums, int m)
{
typedef long long LL;
LL low = nums[0], high = 0;
for(auto i : nums){
high += i;
low = low >= i ? low : i;
}
while(low < high){
LL mid = (low + high) >> 1;
LL temp = 0;
int cnt = 1; // 初始值必须为1
for(auto i : nums){
temp += i;
if (temp > mid){
temp = i;
++cnt;
}
}
if (cnt > m)
low = mid + 1;
else
high = mid;
}
return low;
}
欢迎交流,批评指正!