二分算法是一种高效的查找与优化方法,常用于解决搜索范围明确的优化问题。本篇文章以一道使用二分算法解决的题目为例,一步步推导出如何使用二分解决该问题,并总结出使用到二分算法的题目的一些特征,帮助大家掌握如何灵活运用二分算法解题。
问题描述
小R需要在 days 天内将一批包裹从一个港口运送到另一个港口。传送带上的每个包裹的重量由数组 weights 表示,第 i 个包裹的重量为 weights[i]。
每一天,小R按照包裹在 weights 中的顺序装载包裹,装载的总重量不会超过船的最大运载能力。为了在规定的天数内完成运输任务,小R希望知道船的最低运载能力是多少,才能确保所有包裹能够在 days 天内全部送达。
测试样例
样例1:
输入:
weights = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1],days = 5
输出:15
样例2:
输入:
weights = [3, 2, 2, 5, 1, 4],days = 2
输出:10
样例3:
输入:
weights = [1, 2, 3, 8, 4],days = 4
输出:8
解法
思路分析
问题要求找到最低的运载能力,使得所有包裹在 days 天内运送完成。为此,我们需要探索运载能力 capacity 的取值范围,并设计一个判定方法来检验某一运载能力是否可行。
核心约束
- 包裹必须按顺序装载,不能改变顺序。
- 每天装载的总重量不能超过船的运载能力。
问题可以转化为:在给定的运载能力下,是否可以在 days 天内完成运输。
二分算法设计
运载能力的最低值为 max(weights)(单个包裹的最大重量),因为船至少要能运送最重的包裹;最高值为 sum(weights)(所有包裹的重量之和),对应将所有包裹一次运送。在上述范围内,通过二分查找确定最低的可行运载能力。
定义一个函数 check(weights, days, capacity):
- 初始化天数计数
cnt = 1和当日总重量cur = 0。 - 遍历包裹重量数组:
- 若当前包裹重量
w加上cur不超过capacity,则将其加入当前天。 - 否则,分配到下一天,
cnt++。
- 若当前包裹重量
- 检查
cnt是否超过days,若超过则返回false,否则返回true。
代码实现
public class Main {
public static boolean check(int[] weights, int days, int capacity) {
int cur = capacity;
int cnt = 1;
for (int w : weights) {
if (w > cur) {
cur = capacity - w;
cnt++;
} else {
cur -= w;
}
}
return cnt <= days;
}
public static int solution(int[] weights, int days) {
int l = 1, r = 0;
for (int w : weights) {
l = Math.max(w, l);
r += w;
}
int mid;
while (l < r) {
mid = (l + r) >> 1;
if (check(weights, days, mid))
r = mid;
else
l = mid + 1;
}
// System.out.println(r);
return r;
}
}
二分算法的特点
通过本题分析,我们总结能够被二分算法解决的问题的几个特点:
1. 单调性
二分算法通常适用于具有某种单调性的问题:
- 若增加某个参数使条件成立,则更大的参数也必定成立(或反之)。例如,在本题中,运载能力越大,越容易在规定天数内完成运输。
2. 明确的搜索范围
问题的解空间需要明确、有限。
- 在本题中,运载能力的搜索范围为
[max(weights), sum(weights)]。
3. 判定函数
需要设计一个逻辑清晰的判定函数,用于验证某一搜索值是否满足题目条件:
- 在本题中,
check判定在给定运载能力下是否可行。