地址:https://leetcode-cn.com/problems/split-array-largest-sum/
「非负整数数组」 是非常关键的信息。
- 考虑分割的两个极端:1、不分割;2、每个数成为 1 个子数组;
- 不分割,因为就一个子数组:子数组各自的和的最大值
= sum(nums)
; - 每个数成为 1 个子数组:子数组各自的和的最大值
= max(nums)
;
- 不分割,因为就一个子数组:子数组各自的和的最大值
遍历一次得到数组的最大值与数组所有元素的和。
- 「子数组各自的和的最大值」介于
[max, sum]
之间;
理解单调性
-
一个重要的性质:分割数越大,「子数组各自的和的最大值」就越小(非递增,满足单调性);
-
因此可以使用二分查找,定位分割数。
-
一种「分割方案(分成几个子数组)」对应了一个「子数组各自的和的最大值」;
-
反过来,一个「子数组各自的和的最大值」对应了一种「分割方案(分成几个子数组)」;
-
它们是一一对应的(关键);
思路整理(绕了个弯子去逼近)
-
先找「子数组各自的和的最大值」的中位数(尝试得到的一个值),看看它对应的「分割方案(分成几个子数组)」是多少;
-
如果"分割方案(分成几个子数组)"比题目要求的 m 还多,说明"子数组各自的和的最大值"较小;
-
所以下一次搜索应该至少是中位数
+ 1
(left = mid + 1
),它的反面即至多是中位数(right = mid
)。
Java 代码:
public class Solution {
public int splitArray(int[] nums, int m) {
int max = 0;
int sum = 0;
for (int num : nums) {
max = Math.max(max, num);
sum += num;
}
int left = max;
int right = sum;
while (left < right) {
int mid = (left + right) >>> 1;
int splits = split(nums, mid);
if (splits > m) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
/***
*
* @param nums 原始数组
* @param maxSum 子数组各自的和的最大值
* @return 满足不超过"子数组各自的和的最大值"的分割数
*/
private int split(int[] nums, int maxSum) {
// 至少是一个分割
int splits = 1;
// 当前区间的前缀和
int curPreSum = 0;
for (int num : nums) {
// 尝试加上当前遍历的这个数,如果加上去超过了"子数组各自的和的最大值",就不加这个数,另起炉灶
if (curPreSum + num > maxSum) {
curPreSum = 0;
splits++;
}
curPreSum += num;
}
return splits;
}
public static void main(String[] args) {
int[] nums = new int[]{
7, 2, 5, 10, 8};
int m = 2;
Solution solution = new Solution();
int res = solution.splitArray(nums, m);
System.out.println(res);
}
}