题目:给出一个由非负整数组成的数组和整数m,把该数组切分成m个非空连续子数组,并最小化m个子数组总值最大值。
注意:1 ≤ n ≤ 1000,1 ≤ m ≤ min(50, n) 其中n为数组长度。
例子:
输入:nums = [7,2,5,10,8]
m = 2
输出:18
解题思路:显然,无论怎么切分数组,各子数组的总值中的最大值不小于数组中最大的元素。那么就有一个输出值的下界,而上届就是数组中各元素之和,即m=1时。用二分法更新上下界,最后找到输出值。说到二分法,那么就会出现middle值,每次如何确定是将上界upper更新为middle呢还是将下界lower更新为middle+1呢?
当以middle值为界限切分数组时,即每个子数组总值小于等于middle,需要m*次切割才能完成对数组的切割,此时将m*与输入的m比较,若m*>m,则说明middle值过小,需要更新下界来增加middle值,从而增加子数组的容量减少切分次数;若m*<=m,则说明子数组的容量足够,可以在m次切割内对数组的切割,则可以尝试使用更小的middle值,此时更新上界。
代码如下:
class Solution {
public:
int splitArray(vector<int>& nums, int m) {
int maxVal=0;
long int sum=0;
for(int i = 0; i < nums.size(); ++i){
sum += nums[i];
if(maxVal < nums[i]) maxVal = nums[i];
}
if(m==1) return sum;
return binsearch(nums,m,maxVal,sum);
}
int binsearch(vector<int>& nums, int m, int maxVal, long int sum){
int lower = maxVal;
long int upper = sum;
while(lower < upper){
long int middle = lower + (upper - lower)/2;
if(passIt(nums,m,middle)) {
upper = middle;
}
else {
lower = middle + 1;
}
}
return upper;
}
bool passIt(vector<int>& nums, int m, long int middle){
int num = 1,sub = 0;
for(int i = 0; i < nums.size(); ++i){
if(sub + nums[i] <= middle){
sub += nums[i];
}
else{
num++;
sub = nums[i];
if(num > m) return false;
}
}
return true;
}
};