# 5分钟带你领略：写一个二分查找为什么让面试者挂的这么惨？

12,364

## 难点一: 边界判断

``````let binarySearch = (arr, target) => {
let begin = 0;
let end = ???;
while(???) {
int mid = begin + (end -begin) / 2;
if(arr[mid] == target) {}
else if(arr[mid] > target) {}
else if(arr[mid] < target) {}
}
return ???;
}
``````

``````let search = (arr, target) => {
let begin = 0;
let end = arr.length-1; //写成这样，相当于搜索区间为[begin, end]，这是一个闭区间
while(begin <= end) {//重点: 因为闭区间，所以到了begin等于end时，其实区间内还有一个值要判断，
//因此只有begin>end的时候才能停止
let mid = (begin + end) >>> 1;//位运算，无符号右移一位，同Math.floor((begin+end)/2)
if(arr[mid] == target) {
return mid;
}
else if(arr[mid] > target) {
end = mid - 1;//因为是闭区间，搜索范围变为[left, mid - 1]
}
else if(arr[mid] < target) {
begin = mid + 1; //搜索范围变成[mid + 1, end]
}
}
return -1;
}
``````

``````let search = (arr, target) => {
let begin = 0;
let end = arr.length; //写成这样，相当于搜索区间为[begin, end)，这是一个前闭后开的区间
while(begin < end) {//重点:
//因为前闭后开的区间，所以到了begin等于end时，其实区间内已经没有值了，直接停止
let mid = (begin + end) >>> 1;
if(arr[mid] == target) {
return mid;
}
else if(arr[mid] > target) {
end = mid;//因为是闭区间，搜索范围变为[left, mid - 1]
}
else if(arr[mid] < target) {
begin = mid + 1; //搜索范围变成[mid + 1, end]
}
}
return -1;
}
``````

``````let search = (nums, target) => {
let helpSearch = (nums, begin, end, target) => {
if(begin > end) return -1;
let mid = (begin + end) >>> 1;
if(nums[mid] == target) return mid;
else if(nums[mid] > target)
return helpSearch(nums, begin, mid - 1, target);
else
return helpSearch(nums, mid+1, end, target);
}
//闭区间形式
return helpSearch(nums, 0,  nums.length - 1, target);
}
``````
``````let search = (nums, target) => {
let helpSearch = (nums, begin, end, target) => {
if(begin >= end) return -1;
let mid = (begin + end) >>> 1;
if(nums[mid] == target) return mid;
else if(nums[mid] > target)
return helpSearch(nums, begin, mid, target);
else
return helpSearch(nums, mid+1, end, target);
}
//前闭后开区间形式
return helpSearch(nums, 0,  nums.length, target);
}
``````

## 难点二: 目标值重复的情况

O(log n)级别的时间复杂度，说白了就是二分查找，也就是说，我们需要用二分查找的方式找到目标值最左边的位置和最右边的位置, 其实还是有一些复杂的。

### 寻找左边界位置

``````let left = 0;
let mid;
let right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
right = mid; //重点
}
}
//分析:目前的nums[left]一定是大于等于target的值
if (left == nums.length) return -1;
else return nums[left] == target ? left : -1;
``````

### 寻找右边界位置

``````let left = 0;
let right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
left = mid + 1; //注意
}
}
//分析: 目前的nums[left]左边的部分都是大于等于target的部分，因此我们取nums[left - 1]
if (left == 0) return -1;
else return nums[left - 1] == target ? left - 1: -1;
``````

### 最后代码展示

``````var searchRange = function(nums, target) {
let left = 0;
let mid;
let right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
right = mid;
}
}
let leftIndex = -1, rightIndex = -1;
if (left == nums.length) return [-1, -1];
else leftIndex = nums[left] == target ? left : -1;

left = 0; right = nums.length;
while(left < right) {
mid = (left + right) >>> 1;
if (nums[mid] > target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] == target) {
left = mid + 1;
}
}
if (left == 0) return [-1, -1];
else rightIndex = nums[left - 1] == target ? left - 1: -1;

return [leftIndex, rightIndex];
};
``````