题目:410. 分割数组的最大值
给定一个非负整数数组nums
和一个整数m
,你需要将这个数组分成m
个非空的连续子数组。
设计一个算法使得这 m
个子数组各自和的最大值最小。
示例 1:
输入:nums = [7,2,5,10,8], m = 2
输出:18
解释:
一共有四种方法将 nums 分割为 2 个子数组。
其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
示例 2:
输入:nums = [1,2,3,4,5], m = 2
输出:9
示例 3:
输入:nums = [1,4,4], m = 3
输出:4
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 106
1 <= m <= min(50, nums.length)
解题思路-二分法
思路-二分法
通过题干给出的非负整数数组、非空、连续,可以考虑使用二分法解决问题。
- 如果设置【数组各自和的最大值】很大,必然导致分割数很小
- 如果设置【数组各自和的最大值】很小,必然导致分割数很大
所以可以通过调整【数组各自和的最大值】来达到使分割数等于m
的效果。
注意:
如果某个【数组各自和的最大值】恰好使分割数为m
,此时不能放弃搜索,因为要使这个最大值最小化。此时还应该继续缩小这个【数组各自和的最大值】,使分割数超过m
,最后一个使分割数为m
的值就是要求的最小值
举例:
如题:[7, 2, 5, 10, 8]
, m = 2
- 设置【数组各自和最大值】为
21
,分隔为[7, 2, 5, | 10, 8]]
,m = 2
; - 设置【数组各自和最大值】为
20
,分隔为[7, 2, 5, | 10, 8]]
,m = 2
; - 设置【数组各自和最大值】为
19
,分隔为[7, 2, 5, | 10, 8]]
,m = 2
; - 设置【数组各自和最大值】为
18
,分隔为[7, 2, 5, | 10, 8]]
,m = 2
; - 设置【数组各自和最大值】为
17
,分隔为[7, 2, 5, | 10, | 8]]
,m = 3
;
所以18
是问题的最小值
步骤
- 设置
minSum
和maxSum
,求出【子数组各自和的最大值】的上下界(数组中的最大元素和数组总和),分别对应二分法left
和right
的初始值 - 构造求分割数的函数:
- 遍历数组,叠加元素,如果超出了当前界限,分割数+1;
- 二分法
- 求出
mid
,作为分隔的界限,求出分割数 - 如果分割数大于
m
,说明【子数组各自和的最大值】太小,缩小下界限 - 否则缩小上界限
- 求出
- 返回下界
代码
class Solution {
public:
// 1. 设置`minSum`和`maxSum`,求出【子数组各自和的最大值】的上下界(数组中的最大元素和数组总和),分别对应二分法`left`和`right`的初始值
// 2. 构造求分割数的函数:
// 1. 遍历数组,叠加元素,如果超出了当前界限,分割数+1;
// 3. 二分法
// 1. 求出`mid`,作为分隔的界限,求出分割数
// 2. 如果分割数大于`m`,说明【子数组各自和的最大值】太小,缩小下界限
// 3. 否则缩小上界限
// 4. 返回下界
int split(vector<int>& nums, int mid) {
int split = 1, sum = 0;
for (int num:nums) {
if (sum + num > mid) {
sum = 0;
split++;
}
sum += num;
}
return split;
}
int splitArray(vector<int>& nums, int m) {
int minSum = 0, maxSum = 0;
for (int num:nums) {
minSum = max(minSum, num);
maxSum += num;
}
int left = minSum, right = maxSum;
while (left < right) {
int mid = left + (right - left) / 2;
int splits = split(nums, mid);
if (splits > m) left = mid + 1;
else right = mid;
}
return left;
}
};