上一章节讲了二分查找和左右边界的查找,其实一些题目是可以使用二分查找的思想来解答的,但是它不会把所有的条件准备好让我们来使用二分查找,这就需要我们自己去创造条件来使用二分查找,下面通过一道题目来具体了解:
二分查找应用初体验
875. 爱吃香蕉的珂珂:珂珂喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。
珂珂可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)。
思路:速度k和吃完的时间h是成反比的,所以是满足二分查找的条件的。那么吃的速度最小是每小时1根,最大是每小时吃掉最大那堆的根数,所以我们的查找范围也已经找到了。我们的target自然是h小时,越接近h小时越好,所以我们只需要根据速度中位数算出时间,然后与target做对比,移动left和right即可,注意求的是最小速度,即左边界:
// 时间与速度的关系函数:
// 速度是speed时 吃完所有苹果需要的时间
function getTime(speed: number, piles: number[]): number {
let hours: number = 0;
for (let i = 0; i < piles.length; i++) {
hours += Math.floor((piles[i] + speed - 1) / speed)
}
return hours;
}
function minEatingSpeed(piles: number[], h: number): number {
let left = 1;
let right = Math.max(...piles);
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (h >= getTime(mid, piles)) {
// 寻找左边界所以相等是 right = mid - 1
// 由于speed与h是负相关 所以小于target时也是收缩右边界
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
上述代码还需要解释的是hours的计算,吃掉一堆香蕉的时间其实是pile/speed,但是由于吃掉一堆只能是整数时间,所以为了取整将pile/speed转换成:(pile + speed - 1) / speed配合向下取整。当然如果你还是不明白这种转换思路,你大可以使用if...else来判断是否整除然后未被整除的向上取整。
总结
当题目符合二分查找的条件时:为了方便与target进行对比,我们会抽象出所求x的f(x)函数;然后找到x的取值范围,初始化left和right;最后根据题目判断出是求解左边界还是右边界,套用对应的模板即可;
练习
1011. 在 D 天内送达包裹的能力:传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。
思路:我们要求的是船的最低运载能力,所以我们要写出关于运载能力与天数的关系函数:
function getDays(carring: number, weights: number[]): number {
let day: number = 0;
let curCarring = 0;
for(let i = 0; i < weights.length; i++) {
curCarring += weights[i]
if (curCarring > carring) {
day++
curCarring = weights[i]
} else if (curCarring == carring) {
day++
curCarring = 0
}
}
if (curCarring) day++
return day;
}
然后我们找出运载能力的范围,最低运载left肯定是weights中的最大值,最大运载是一船运完所有的货物,我们可以取left * weights.length,当然你也可以遍历数组求出总和。由于运载能力与所需天数是负相关,所以我们要找的是左边界,所以:
function shipWithinDays(weights: number[], days: number): number {
let left = Math.max(...weights);
let right = left * weights.length;
while(left <= right) {
const mid = Math.floor((left + right) / 2);
if (getDays(mid, weights) <= days) {
right = mid - 1
} else {
left = mid + 1
}
}
return left;
};