二分查找方法详解
一.二分查找框架
int binarySearch(int[] nums,int target){
int left = 0,right = ...;
while(...){
int mid = left +(right - left)/2;
if(nums[mid] == target){
...
}
else if (nums[mid] <target){
left = ...;
}
else if (nums[mid]>target){
right = ...;
}
}
return ...;
}
这个基本框架有以下几点需要注意:
- 不要使用else,而是使用else if 将所有情况都写清楚。
- 其中…标记的部分,就是可能出现细节问题的地方。多关注这些地方的变化。
- 计算mid时要防止溢出。代码中
left+(right-left)/2
就和(left+right)/2
的结果相同,但是有效防止了left和right太大直接相加导致溢出。
二.寻找一个数(基本的二分搜索)
即搜索一个数,如果存在,返回其索引,否则返回-1。
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 -1;
}
循环的终止条件是搜索区间为空的时候终止。
如果 right = nums.length;
那么while循环的判断条件应该为:while(left<right)
,这时候就需要在return的时候再做一个判断。
while(left<right){
//...
}
return nums[left] == target?left:-1;
为什么是mid+1和mid-1?
根据刚才明确的【搜索区间】,本算法的搜索区间是[left,right],当我们发现索引mid不是要找的target,下一步应该去搜索哪里?肯定是搜索[left,mid-1]或者[mid+1,right],因为mid已经搜索过,应该从搜索区间中去除。
此算法框架的缺陷: 如果一个有序数组nums=[1,2,2,2,3],target为2,此算法返回的索引是2。但是如果想得到的是target的左侧边界,即索引1,或者想得到target的右侧边界,即索引3,该算法无法保证二分查找对数级的复杂度。
三. 寻找左侧边界的二分搜索
以下是常见的代码形式,其中的标记是需要注意的细节:
int left_bound(int[]nums,int target){
if(nums.length == 0) return -1;
int left = 0;
int right = nums.length-1;
while(left<=right){
int mid = left + (right-left)/2;
if (nums[mid] == target){
right = mid-1;
}
else if (nums[id]<target){
left = mid+1;
}
else if (nums[id]>target)
right = mid-1;
}
if(left>=nums.length || nums[left]!=target)
return -1;
return left;
}
为什么该算法能够搜索左侧边界?
当找到target时,不返回对应的索引,而是缩小【搜索区间】的右边界right,不断向左收缩,这样最后跳出的时候就是target的左边界。
因为while的退出条件是left == right+1,所以有可能出现,left>nums.length的情况,所以需要对越界情况进行判断。
四.寻找右侧边界的二分查找
int right_bound(int []nums,int target){
if(nums.length ==0 ) return -1;
int left =0,right = nums.length-1;
while(left<=right){
int mid = left +(right -left)/2;
if(nums[mid] == target)
left = mid+1;
else if (nums[mid] >target)
right = mid-1;
else if(nums[mid]<target)
left = mid+1;
}
if(right<0 || nums[right]!=target)
return -1;
return right;
}