LeetCode-410 Split Array Largest Sum|分割数组的最大值
题目描述
给定一个非负整数数组和一个整数 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,在所有情况中最小。
题目分析
该题目需要根据给出的m将数组分割,并且使分割出的子数组和的最大值最小。
我们可以考虑使用二分搜索,找到最大和的分割范围,根据二分查找缩小目标值的范围,直到找到这个目标值。
根据题意可以得知,我们将数组分割后的这m个连续子数组的最大和的范围。
我们可以假设出两种情况:
- 当m为1时,此时分割的子数组就是数组本身,这时子数组最大和就是数组所有元素的和
- 当m为数组长度时,此时分割的m个子数组每个子数组中是数组中的一个元素,此时最大值就是数组中最大的数字
根据上述的假设我们可以得到数组最大和的范围:[ max(sums) , sum(sums) ]
我们就可以在这个范围中查找哪个值符合题意的要求。
- 首先取范围中的中值mid
- 遍历数组,并计算遍历过的数组的和,我们贪心的向遍历过的子数组添加尽可能多的元素,直到发现此时和大于中值mid,在这个位置分割数组记录分割次数cut
- 在遍历结束后如果发现cut < m,就说明mid偏大,如果cut值增大,那么子树组的最大值还会减少,所以就需要将mid赋值给二分查找的上限upper,减小搜索范围。反之就将mid赋值给下限lower
代码
class Solution {
//每种可能性中数组数组和的最大值范围是[max(sums),sum(sunms)]
//lower: max(sums)
//sum: sum(sums)
//即结果可能出现的范围是: max(sums) <= ans <= sum(sums)
//假设目前检验值为ans
//维护一个sum(子数组的和), cut(切的刀数)
//当sun > ans时,说明子数组已经超过ans,则cut++,sum清零
//继续循环
//当退出循环时,如果cut < m,就说明目标结果在ans的左边
//则将mid设为upper
//反之,将mid设为lower
public int splitArray(int[] nums, int m) {
int lower = 0;
int upper = 0;
for(int i = 0; i < nums.length; i++){
upper += nums[i];
if(nums[i] > lower){
lower = nums[i];
}
}
while(lower + 1 < upper){
int mid = lower + (upper - lower) / 2;
if(check(nums, m, mid)){
upper = mid;
}else{
lower = mid;
}
}
if(check(nums, m, lower)){
return lower;
}else{
return upper;
}
}
public boolean check(int[] nums, int m, int ans){
int sum = 0;
int cut = 1;
for(int i = 0; i < nums.length; i++){
if(sum + nums[i] > ans){
cut ++;
sum = nums[i];
}else{
sum += nums[i];
}
}
if(cut <= m){
return true;
}else{
return false;
}
}
}