LeetCode题目
力扣 | 难度 |
---|---|
1011. 在 D 天内送达包裹的能力 | 🟠 |
410. 分割数组的最大值 | 🔴 |
875. 爱吃香蕉的珂珂 | 🟠 |
剑指 Offer II 073. 狒狒吃香蕉 | 🟠 |
解题分析
这类题目可能的解空间在一个范围内,不停二分遍历可能解。找到一个可行解后还要继续找,直到“最小值”的可行解。类比到二分搜索算法中就是满足条件的最左值。
以”410. 分割数组的最大值“问题为例,可能解空间在“最大的单个元素”和“所有元素和”之间。然后二分查找可行解空间,一个可行解是分组数等于或者小于目标组数。“小于目标组数”成立的原因是可以继续拆分,拆分后子数组元素和一定满足条件,显然这不一定是最优解。找到可行解后如果继续优化条件不成立,那么上一次的可行解就是最优解。
从可行解中找最左值“二分搜索”中边界条件很容易出错。比如下面找最左值的方式:
while (left < right) {
int mid = left + (right - left) / 2;
int splits = split(nums, mid);
if (splits > m) {
// 如果分割数太多,说明「子数组各自的和的最大值」太小,此时需要将「子数组各自的和的最大值」调大
// 下一轮搜索的区间是 [mid + 1, right]
left = mid + 1;
} else {
// 下一轮搜索的区间是上一轮的反面区间 [left, mid]
right = mid;
}
}
个人更喜欢下面的方式,容易理解,不易出错,缺点是代码有点冗余。
while(left <= right) {
int mid = left + (right - left) / 2;
int groups = split(nums, mid);
if(groups > k) {
// 分组和太小,分组数大于预定值
left = mid + 1;
} else if(groups < k) {
// 分组和太大,分组数小于预定值
right = mid - 1;
} else {
// 可能分组数 < k 走不到这里,因为 split 是贪心算法。但实际上可以从 < k 拆分到 k 组
if(mid == max || split(nums, mid -1) > k) {
return mid;
} else {
right = mid - 1;
}
}
}
package org.stone.study.algo.ex202403;
/**
* [410. 分割数组的最大值](https://leetcode.cn/problems/split-array-largest-sum/)
* * 给定一个非负整数数组 nums 和一个整数 k ,你需要将这个数组分成 k 个非空的连续子数组。
* 设计一个算法使得这 k 个子数组各自和的最大值最小。
*/
public class SplitArrayWithMinSum {
public static void main(String[] args) {
int[] nums = new int[] {
7,2,5,10,8};
int k = 2;
// ans: 18
System.out.println("ans:" + new SplitArrayWithMinSum().splitArray(nums, k));