# 一文弄懂二分查找

·  阅读 240

## 问题引入

先从下面“类二分查找”代码中找出正确的二分查找代码：

``````int search(int[] nums,int target){
int low=0,high=nums.length;
while(low < high){
int mid = low + (high-low)/2;
if(nums[mid] < target){
low = mid + 1;
}
else {
high = mid;
}
}
return left;
}

``````int search(int[] nums,int target){
int low=0,high=nums.length-1;
while(low < high){
int mid = low + (high-low)/2;
if(nums[mid] < target){
low = mid + 1;
}
else {
high = mid;
}
}
return left;
}

``````int search(int[] nums,int target){
int low=0,high=nums.length;
while(low <= high){
int mid = low + (high-low)/2;
if(nums[mid] < target){
low = mid + 1;
}
else {
high = mid;
}
}
return left;
}

``````int search(int[] nums,int target){
int low=0,high=nums.length;
while(low < high){
int mid = low + (high-low)/2;
if(nums[mid] <= target){
low = mid + 1;
}
else {
high = mid;
}
}
return left;
}

1. high初始化为nums.length还是nums.length-1?
2. while里是low<high还是low<=high?
3. nums[mid]<target还是nums[mid]<=target?
4. low = mid还是low=mid+1? high=mid还是mid-1?
5. 最后return left还是right?

## 前提

本文所述的二分查找返回一个下标值，使得在此位置插入新元素后列表仍然有序。（所谓插入，就是将插入位置及之后的元素全部向后移动一位，而原插入位置则赋值为新元素）这个位置是一定存在的，但不一定是唯一的。倘若数组nums的长度为n，本文给出的二分查找函数返回结果是[0,n]。

## lower_bound

``````int search(int[] nums,int target){
int low=0,high=nums.length;
while(low < high){
int mid = low + (high-low)/2;
if(nums[mid] < target){
low = mid + 1;
}
else {
high = mid;
}
}
return left;
}

这是查找lower_bound的代码，也就是第一个大于等于target的代码，由于target可能大于数组里所有数，所以high初始化为nums.length。我们先看这部分

``````if(nums[mid] < target){
low = mid + 1;
}

``````else {
high = mid;
}

else也就是nums[mid] >= target，此时，将high更新为mid，由于我们找的是第一个大于等于target的元素，而nums[mid] >= target，也就是说mid一定是大于等于target的坐标集合里的，但是是不是第一个不知道。令high = mid，high其实代表的就是可能可以的解。再看循环条件：

``````while(low < high)

## upper_bound

``````int search(int[] nums,int target){
int low=0,high=nums.length;
while(low < high){
int mid = low + (high-low)/2;
if(nums[mid] <= target){
low = mid + 1;
}
else {
high = mid;
}
}
return left;
}

## 查找元素出现位置

``````if(low == nums.length||nums[low]!=target)//数组里没这个元素
return -1;
return low  //否则，数组里有这个元素，第一个大于等于target的位置也就是第一个等于target的位置

## 一些细节问题

1. high初始化为nums.length还是nums.length-1? high初始化为nums.length，因为有可能target比数组所有的数都大，所以nums.length也是一个可能符合条件的位置。
2. while里是low<high还是low<=high? low<high,因为要确保循环结束时，low == high
3. nums[mid]<target还是nums[mid]<=target? 这个因题目而异，主要看题目要求什么，不符合条件的情况是什么
4. low = mid还是low=mid+1? high=mid还是mid-1? low = mid + 1,low控制的是不符合条件的解，mid也是不符合条件的；high = mid，因为high控制的是符合条件的解
5. 最后return low还是high? 都一样

## 查找元素的左右边界

34. 在排序数组中查找元素的第一个和最后一个位置

``````输入： nums = [5,7,7,8,8,10], target = 8

``````public int[] searchRange(int[] nums, int target) {
if(nums.length==0)
return new int[]{-1,-1};
int low=0,high=nums.length;
//寻找lower_bound
while(low<high){
int mid = low + (high-low)/2;
if(nums[mid]<target){
low = mid + 1;
}
else {
high = mid;
}
}
//pos1记录lower_bound
int pos1 = low;
//重置low和high,进行下一次循环，寻找upper_bound
low=0;
high=nums.length;
//寻找upper_bound
while(low<high){
int mid = low + (high-low)/2;
if(nums[mid]<=target){
low = mid + 1;
}
else {
high = mid;
}
}
//此时的low就是upper_bound
if (low== pos1)
return new int[]{-1,-1};
return new int[]{pos1,low-1};
}

## 第一个错误的版本

278. 第一个错误的版本

``````输入： n = 5, bad = 4

``````public class Solution extends VersionControl {
//新的二分查找，太好用了
int low =1,high=n;
while(low<high){
int mid = low + (high-low)/2;
low = mid + 1; //low向右压缩，对不符合条件的解进行淘汰
}
else {
high = mid; //high向左压缩，记录符合条件的解
}
}
return high;
}
}

## 总结

``````private static int binarySearch0(int[] a, int fromIndex, int toIndex,int key) {
int low = fromIndex;
int high = toIndex - 1;

while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];

if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}