二分查找
模板题 704. 二分查找
二分查找是一种效率较高的方法(logN
) , 它的要求是线性表中元素是有序的
我们常见的写法有:
public int binarySearch(int[] nums , int target){
int left = 0 , right = nums.length - 1;
while(left <= right){
//left > right则退出,此时搜索区间为[left , right]
int mid = left + (right - left) / 2;//(left + right) / 2
if (nums[mid] == target){
return mid;
}else if (nums[mid] > target){
right = mid - 1;
}else if (nums[mid] < target){
left = mid + 1;
}
}
return -1;
}
另一种写法:
public int binarySearch(int[] nums , int target){
int left = 0 , right = nums.length;
while(left < right){//difference
//left == right则退出,此时搜索区间为[left , right)
int mid = left + (right - left) / 2;
if (nums[mid] == target){
return mid;
}else if (nums[mid] > target){
right = mid;
}else if (nums[mid] < target){
left = mid + 1;
}
}
return -1;
}
从两种写法中,我们可以得知有两个不同:循环条件的退出
以及搜索区间
,这是二分查找的易错点。
边界问题没处理好导致死循环,搜索区间没处理好则把结果去除了。
二分查找的另一种用法,搜索左右边界
模板题 34. 在排序数组中查找元素的第一个和最后一个位置
通常我是把左右边界分开写,因为我怕逻辑出错导致WA,这里我用的是[Left , Right]
去寻找左右边界。
而我们需要思考在什么时候更新下一个搜索区间,才可以逼近左/右边界
- 寻找左边界
- nums[mid] > target , 此时说明未找到target,缩小区间
- nums[mid] == target , 此时说明找到target,更新right ,但没有确定是不是边界
- 寻找右边界
- 同理,我们在nums[mid] == target的时候更新left
第一种写法[left , right]
public int[] searchRange(int[] nums, int target) {
if (nums.length == 0) return new int[]{-1, -1};
int lower = lowerBound(nums, target);
int upper = upperBound(nums, target);
return new int[]{lower, upper};
}
private int lowerBound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {//1.
right = mid - 1;
} else {
left = mid + 1;
}
}
if (left >= nums.length || nums[left] != target) {//检查元素是否符合
return -1;
}
return left;
}
private int upperBound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {//2.
left = mid + 1;
} else {
right = mid - 1;
}
}
if (right < 0 || nums[right] != target) {//检查元素是否符合
return -1;
}
return right;
}
第二种写法[left , right)
public int[] searchRange(int[] nums, int target) {
if (nums.length == 0) return new int[]{-1, -1};
int upper = upper(nums, target);
int lower = lower(nums, target);
return new int[]{lower, upper};
}
private int lower(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid;
} else {
left = mid + 1;
}
}
if (left >= nums.length || nums[left] != target) {
return -1;
}
return left;
}
private int upper(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 {
right = mid;
}
}
if (left == 0 || nums[left - 1] != target) return -1;
return left - 1;//return right
}