1.二分查找通用模板
二分查找的前提条件:保证元素有序
public int binarySerach(int[] nums, int target) {
// 定义初始边界
int left = 0;
int right = ... ;
// 开始查找
while(...) {
// 找寻中点
int mid = left + (right - left) / 2; //防止计算溢出
// 判断中点和目标的大小
if (nums[mid] == target) {
...
}
else if (nums[mid] < target) {
...
}
else if (nums[mid] > target) {
...
}
}
// 循环结束,没找到目标元素,返回相应结果
return ... ;
}
上面中的模板留白部分,其实有很多细节要深入讨论,不然极有可能导致二分查找失败
2.1 搜索区间
搜索区间指的是 left 和 right 的下标,通常有两种情况:
(1)左右皆闭 [left,right];
(2)左闭右开 [left, right)。
不同的搜索区间的应用场景多不相同。第一种通常适合查找数组中的指定单个元素,第二种在搜索左右边界时常用。
2.2.1 左右皆闭
public int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return ... ;
}
这种情况下,right 通常赋值为 nums.length-1,right 没有越界,这时我们搜索的是整个 [left, right] 区间:
(1) while循环的判断条件
因此 while 循环的条件常用 <=,这样做在最后的判断之前,left和right指向同一个位置mid,条件判断处理后的区间变为了 [right+1, right],循环终止,正常退出。
当循环条件用 < 时,最后的判断之前,left和right紧挨着,条件判断处理后区间变为了 [right, right],这时候区间非空,循环终止,但仍旧有一个元素没有被搜索,直接返回是错误的。
(2) left和right下标的移动
在本例子左右皆闭中,整个区间是 [left, right], 如果 mid 不是我们要找的target,下一步应去 [left, mid-1] 或者 [mid+1, right],因为 mid 已经被搜索过了
2.2.2 左闭右开
上述全闭空间中找单个元素非常有利,但是一旦找寻左右边界就缺陷率。如给定数组[0,1,2,3,3,3,4],target=3,找寻target的左右边界就有问题了
以下为寻找左侧边界的示例
public int binaryLeftBound(int[] nums, int target) {
int left = 0, right = nums.length; // right指向越界下标,左闭右开区间
while(left < right) { // 最后判断之前区间为[left, right), 已经只有一位元素了nums[left]了
int mid = left + (right - left) / 2;
if(nums[mid] == target) {
right = mid; // 核心部分,缩小上界区间,在[left, mid)中继续搜索
}
else if(nums[mid] < target) {
left= mid+1; // 左闭右开原则
}
else if(nums[mid] > target) {
right = mid; // 左闭右开原则,实际上里面的元素区间到mid-1
}
}
// 防止越界
if(left >= nums.length || nums[left] != target) {
return -1;
}
return left;
}
Q: 为什么能够搜索左边界?
A: 上述代码的一个核心在于 mid 找到目标后,不返回,而是缩小上界区间,在 [left, mid) 中继续搜索,不断左缩
Q:没有找到目标时,如何防止索引越界?
A:在该种情况下,使用左闭右开搜索区间,退出循环时,left=right,如当 target 比 nums 中所有的元素都大时,就会导致 left=right=nums.length,因此要做判断。
搜寻右边界时,模板如下:
public int binaryRightBound(int[] nums, int target) {
int left = 0, right= nums.length;
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target) {
left = mid+1; //搜寻右边界,改变下界区间
}
else if(nums[mid] < target) {
left = mid+1; //左闭右开原则
}
else if(nums[mid] > target) {
right = mid; //左闭右开原则
}
}
// 越界判断,退出循环时,left=right
if(right <= 0 || nums[right-1] != target) {
return -1;
}
return right-1;
}
Q:为什么返回 right-1,而不是right?
A:当结束while循环时,nums[left] 不一定是target。