目录
前言
如果一开始不知道问题的解是什么,但知道解的范围是多少,则可以尝试在这个范围内应用二分查找。
假设解的范围的最小值是 min,最大值是 max,先尝试范围内的中间值 mid。如果 mid 正好是问题的解,那么固然好。当 mid 不是问题的解时,如果能够判断接下来应该在从 min 到 mid - 1 或 mid + 1 到 max 的范围内查找,那么就可以继续重复二分查找的过程,直到找到解为止。
应用这种思路的关键在于两点:一是确定解的范围,即解的可能的最小值和最大值。二是在发现中间值不是解之后如何判断接下来应该在解的范围的前半部分还是后半部分查找。只有每次将查找范围减少一半时才能应用二分查找算法。
面试题 72 : 求平方根
题目:
输入一个非负整数,请计算它的平方根。正数的平方根有两个,只输出其中的正数平方根。如果平方根不是整数,那么只需要输出它的整数部分。例如,如果输入 4 则输出 2;如果输入 18 则输出 4。
分析:
假设输入的非负整数为 n,如果找到一个整数 m,它满足 和 ,那么 m 就是 n 的平方根。
也就是说,要在 0 到 n 的范围内,找到最后一个平方小于或等于 n 的整数 m。
代码实现:
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;
while (left <= right)
{
int mid = (left + right) / 2;
if ((long)mid * mid <= x)
left = mid + 1;
else
right = mid - 1;
}
return right;
}
};
面试题 73 : 狒狒吃香蕉
题目:
狒狒很喜欢吃香蕉。一天它发现了 n 堆香蕉,第 i 堆有 piles[i] 根香蕉。门卫刚好走开,H 小时后才会回来。狒狒吃香蕉喜欢细嚼慢咽,但又想在门卫回来之前吃完所有的香蕉。请问狒狒每小时至少吃多少根香蕉?如果狒狒决定每小时吃 k 根香蕉,而它在吃的某一堆剩余的香蕉的数目少于 k,那么它只会将这一堆的香蕉吃完,下一个小时才会吃另一堆的香蕉。
例如,有 4 堆香蕉,表示香蕉数目的数组 piles 为 [3, 6, 7, 11],门卫将于 8 小时之后回来,那么狒狒每小时吃香蕉的最少数目为 4 根。如果它每小时吃 4 根香蕉,那么它用 8(1 + 2 + 2 + 3)小时吃完所有香蕉。如果它每小时只吃 3 根香蕉,则需要 10(1 + 2 + 3 + 4)小时,不能在门卫回来之前吃完。
分析:
虽然还不知道狒狒每小时至少吃几根香蕉才能在门卫回来之前吃完所有的香蕉,但知道它吃香蕉的速度的范围。首先,它每小时至少吃 1 根香蕉。其次,由于它每小时最多只能吃一堆香蕉,因此它每小时吃香蕉数目的上限是最大一堆香蕉的数目,记为 max 根。
也就是说,狒狒吃香蕉的速度应该在每小时 1 根到每小时 max 根的范围内。
在 1 ~ max 根取中间值 mid 根,求出按照每小时吃 mid 根香蕉的速度吃完所有香蕉的时间。如果需要的时间大于 H 小时,则意味着它应该吃得更快一些,因此狒狒吃香蕉的速度应该在每小时 mid + 1 根到 max 根这个范围内。如果需要的时间小于或等于 H 小时,那么每小时 mid 根可能是最慢的速度。
因此,二分查找的目标是找到第 1 个吃完所有香蕉需要的时间小于或等于 H 小时的速度。
代码实现:
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int h) {
int max = piles[0];
for (int i = 1; i < piles.size(); ++i)
{
if (piles[i] > max)
max = piles[i];
}
int left = 1;
int right = max;
while (left <= right)
{
int mid = left + (right - left) / 2;
int hours = 0;
for (int pile : piles)
{
hours += (pile + mid - 1) / mid;
}
if (hours <= h)
right = mid - 1;
else
left = mid + 1;
}
return left;
}
};
该算法的时间复杂度是 O(nlogn)。