Given an array nums 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.
Example 1:
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.
// 在每个子数组和不超过 max 的条件下,
// 计算 nums 至少可以分割成几个子数组
int split(int[] nums, int max);
/* 主函数,计算最大子数组和 */
int splitArray(int[] nums, int m) {
int lo = getMax(nums), hi = getSum(nums);
for (int max = lo; max <= hi; max++) {
// 如果最大子数组和是 max,
// 至少可以把 nums 分割成 n 个子数组
int n = split(nums, max);
// 为什么是 <= 不是 == ?
if (n <= m) {
return max;
}
}
return -1;
}
/* 辅助函数,若限制最大子数组和为 max,
计算 nums 至少可以被分割成几个子数组 */
int split(int[] nums, int max) {
// 至少可以分割的子数组数量
int count = 1;
// 记录每个子数组的元素和
int sum = 0;
for (int i = 0; i < nums.length; i++) {
if (sum + nums[i] > max) {
// 如果当前子数组和大于 max 限制
// 则这个子数组不能再添加元素了
count++;
sum = nums[i];
} else {
// 当前子数组和还没达到 max 限制
// 还可以添加元素
sum += nums[i];
}
}
return count;
}
// 计算数组中的最大值
int getMax(int[] nums) {
int res = 0;
for (int n : nums)
res = Math.max(n, res);
return res;
}
// 计算数组元素和
int getSum(int[] nums) {
int res = 0;
for (int n : nums)
res += n;
return res;
}
int lo = getMax(nums), hi = getSum(nums);
for (int max = lo; max <= hi; max++) {
int n = split(nums, max);
if (n <= m) {
return max;
}
}
int splitArray(int[] nums, int m) {
// 一般搜索区间是左闭右开的,所以 hi 要额外加一
int lo = getMax(nums), hi = getSum(nums) + 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
// 根据分割子数组的个数收缩搜索区间
int n = split(nums, mid);
if (n == m) {
// 收缩右边界,达到搜索左边界的目的
hi = mid;
} else if (n < m) {
// 最大子数组和上限高了,减小一些
hi = mid;
} else if (n > m) {
// 最大子数组和上限低了,增加一些
lo = mid + 1;
}
}
return lo;
}
int split(int[] nums, int max) {/* 见上文 */}
int getMax(int[] nums) {/* 见上文 */}
int getSum(int[] nums) {/* 见上文 */}
Another Version:
class Solution {
public int splitArray(int[] nums, int m) {
int start = Integer.MIN_VALUE;
int end = 0;
for(int i = 0;i< nums.length; i++)
{
start = Math.max(start, nums[i]);
end += nums[i];
}
while(start +1<end)
{
int mid = start+ (end-start)/2;
if(split(nums,mid) <= m)
{
end = mid;
}
else
{
start = mid;
}
}
if(split(nums, start) <= m) return start;
if(split(nums, end) <= m) return end;
return -1;
}
// 在每个子数组和不超过 max 的条件下,
// 计算 nums 至少可以分割成几个子数组
public int split(int[] nums, int max)
{
int result = 1;
int sum = 0;
for(int i = 0; i< nums.length; i++)
{
if(sum+nums[i] <= max)
{
sum += nums[i];
}
else
{
sum = nums[i];
result++;
}
}
return result;
}
}