Question
Given an array which consists of non-negative integers and an integer m, you can split the array into m non-empty continuous subarrays. Write an algorithm to minimize the largest sum among these m subarrays.
Note
If n is the length of array, assume the following constraints are satisfied:
1 ≤ n ≤ 1000
1 ≤ m ≤ min(50, n)
Examples
Input:
nums = [7,2,5,10,8]
m = 2
Output:
18
Explanation:
There are four ways to split nums into two subarrays.
The best way is to split it into [7,2,5] and [10,8],
where the largest sum among the two subarrays is only 18.
Analysis
这个题目咋一看会无从下手,其实有两个解决的办法,都非常巧妙。
一种是二分法。一般来说,根据二分的对象不同,二分法有两种思路,一是对定义域进行二分,比如在有序数列中找指定的数;另一种思路是对值域进行二分,比如这个题目。
首先确定二分的上下界,当m=n时,最终的结果就是数组中的最大值,因此这个最大值就是二分的下界。相比于随便取数组中的一个值,以这个最大值为下界还有一个好处,即保证每个子数组至少包含一个数。二分法的上界在m=1时取到,为整个数组的和。
然后确定比较准则。定义另一个函数```check(...)```,判断将数组nums分为m份且每份的和不超过maxs是否可行。我们只需要遍历数组并做累加,当累加的和超过maxs时就归零,同时m--,如果遍历结束后m==1,说明刚好可以分为m份使得每份的和不超过maxs(最后一个子数组没有m--就结束循环了)。此时maxs可以作为新的上界。否则,maxs作为新的下界。具体方法见代码。
另一个方法是动态规划。动态规划的关键是建立递推关系。而这个递推关系也是这个题目比较精髓的地方。一般来说,递推关系可以直接设为要求的值,比如:
- 根据nums划分,D[i,j]表示[num[i], nums[j]]之间的数分为m份时的解,或D[i]表示数组前i个数划分为m份时的解;
- 根据m划分,D[i]表示对整个数组划分为i份时解;
但是仔细考察上面两种方法,均无法建立简单高效的递推关系,因此不可行。这个题目的正确解法是
- 设D[i,j]表示前i个数划分为j份时的解;
- 最终要求的是D[nums.size() - 1,m];
- 递推关系为D[i,j] = min(D[i,j], max(D[k, j-1], sum(nums[p] | k < p <= i)));
为了快速计算累积和,还需要额外一个数组保存预计算的累积和。具体实现细节见代码。
Solution 二分法
class Solution {
public:
int check(vector<int>& nums, int m, long int maxs){
long int count = 0;
for(int i = 0; i < nums.size(); ++i){
count += nums[i];
if(count <= maxs)
continue;
count = nums[i];
m--;
}
if(m >= 1) // maxs is equal or larger than ground truth;
return -1;
else // maxs is smaller than ground truth;
return 1;
}
int splitArray(vector<int>& nums, int m) {
int n = nums.size();
long int left = nums[0];
long int right = 0;
for(int i = 0; i < n; ++i){
right += nums[i];
left = max(nums[i], (int)left);
}
while(left < right){
printf("left: %d, right: %d\n",left,right);
long int mid = (left + right) / 2;
switch(check(nums, m, mid)){
case 1:
left = mid + 1;
break;
case -1:
right = mid;
break;
}
}
return right;
}
};
Solution 动态规划
class Solution {
public:
int splitArray(vector<int>& nums, int m) {
int n = nums.size();
vector<int> sum(n, nums[0]);
vector<vector<int>>pool(n, vector<int>(m + 1, INT_MAX));
for(int i = 1; i < n; ++i){
sum[i] = sum[i - 1] + nums[i];
pool[i][1] = sum[i];
}
pool[0][1] = nums[0];
for(int i = 1; i < n; ++i){
for(int j = 2; j <= min(m, i + 1); ++j){
for(int k = j - 2; k < i; ++k){
pool[i][j] = min(pool[i][j], max(pool[k][j - 1], sum[i] - sum[k]));
}
}
}
for(int i = 0; i < n; ++i){
for(int j = 0; j <= m; ++j){
printf("%d ", pool[i][j]);
}
printf("\n");
}
return pool[n - 1][m];
}
};