二分查找作为程序员的一项基本技能,是面试官最常使用来考察程序员基本素质的算法之一,也是解决很多查找类题目的常用方法,它可以达到 的时间复杂度。
标准二分查找模板
前提条件
必须有序。一般是从小到大有序。
要点
总体上来说:三个变量(左边界、右边界、中间值) + 判断条件(右边界更新条件和左边界更新条件)+ 返回值。
- 循环条件:
left <= right
- 中间位置计算:
mid = left + ((right -left) >> 1)
- 左边界更新:
left = mid + 1
- 右边界更新:
right = mid - 1
- 返回值:
mid(找到) 或者 -1(没找到)
坑点
计算中间值导致的数据越界。一般我们都是定义左边界(left)和右边界(right)都使用 int 类型,如果 left 和 right 足够大,mid = (left + right)/2,可能会由于 left+right 导致 int 数据类型越界。所以安全的写法是 mid = left + ((right - left) >> 1) 或者 mid = left + (right - left) / 2,推荐使用右移操作,因为右移比除法快。
核心代码
需要完成一个合法性检查函数,该函数的目的是使用 mid 这个分界点,根据题目进行合法性判断。如果合法,则右边界更新,看是否有更小的 mid 符合要求;如果不合法,则左边界更新,看是否有更大的 mid 符合要求。
该合法性检查函数原型如下:
bool check(int mid) {
//根据题目要求,写出合法性检查函数
}
难点
最终的查找位置,也就是需要求的 answer 的位置。我们一共有 left、right、mid 这三个位置。根据题意确认哪个变量是我们需要的值。
模板代码
C++模板
class BinarySearch {
private int check(int[] nums, int x, int target) {
if (nums[x] == target) {
return 0;
} else if (nums[mid] > target) {
return 1;
} else {
return -1;
}
}
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (0 == check(nums, mid, target)) return mid;
else if (1 == check(nums, mid, target)) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
}
C模板
/*
输入参数:
nums 要搜索的有序数组
len 数组长度
target 搜索目标
*/
int binary_search(int nums[], int len, int target) {
int left = 0;
int right = len-1;
int mid;
while (left <= right) {
mid = left + ((right - left) >> 1);
if (target == nums[mid]) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
特别说明
1、我们的循环条件中包含了 left == right
的情况,则我们必须在每次循环中改变 left
和 right
的指向,以防止进入死循环。
2、循环终止的条件有两个。一是找到目标;二是没有找到目标,即 left > right 。
3、防止计算 mid 的时候出现数据溢出。参考上面特别说明的“坑点”。
4、left + ((right -left) >> 1)。
对于目标区域长度为奇数而言,是处于正中间的;对于长度为偶数而言,是中间偏左的。因此左右边界相遇时,只会是以下两种情况:
left/mid
,right
(left, mid 指向同一个数,right指向它的下一个数)。left/mid/right
(left, mid, right 指向同一个数)。
即因为 mid
对于长度为偶数的区间总是偏左的,所以当区间长度小于等于 2 时,mid
总是和 left
在同一侧。