- 题意:给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
最小化最大值(最大化最小值)等问题模型,往往都可以用二分解决。
记数组中的最大值为 l l l,数组的元素总和为 r r r。那么显然答案必然在 [ l , r ] [l,r] [l,r]之间。
其次,如果能不超过 x x x的阈值去划分整个数组,那么 [ x + 1 , r ] [x+1,r] [x+1,r]中的所有数都是可行的。
反之,如果 y y y为阈值,那么 [ l , y − 1 ] [l,y-1] [l,y−1]中的所有数都是不可行的。
而这就是二分的两段性。
具体写判定函数的时候,还可以用二分优化。因为我们需要贪心的求出一段数并且这段数的和要小于等于阈值,而因为是非负数数组,所以这又可以用二分求出。
时间复杂度不超过: O ( l o g ( s u m − m m a x ) ∗ n ) O(log(sum-mmax)*n) O(log(sum−mmax)∗n)
class Solution {
public:
vector<int> s; //数据保证不会溢出
int m, n ;
int splitArray(vector<int>& a, int m) {
n = a.size() , this->m = m;
s.resize(n,0);
s[0] = a[0];
for(int i=1;i<n;i++){
s[i] = s[i-1]+a[i];
}
int l = *max_element(a.begin(),a.end()), r = s[n-1];
while(l<r){
int mid = l+(r-l)/2;
if(check(mid,a)){
r = mid;
}else{
l = mid+1;
}
}
return l;
}
bool check(int top,const vector<int>& a){
int cnt = 0, x = 0;
while(x<n){
int l = x, r = n-1;
while(l<r){
int mid = (l+r+1)/2;
int ss = s[mid];
if(x>0) ss -= s[x-1];
if(ss<=top){
l = mid;
}else{
r = mid-1;
}
}
cnt++;
x = l+1;
}
return cnt<=m;
}
};
此题还可以用DP去解决。
DP做法