(版本一)左闭右闭区间
二分查找算法的原理如下: 我们这里使用左闭右闭区间【】
1. 设置查找区间:low = 0;high= n;
2. 若查找区间[low, high]不存在,则查找失败;否则转步骤3
3. 取中间位mid = (low + high) / 2;比较 target 与 arr[mid],有以下三种情况:
3.1 若 target < arr[mid],则high = mid - 1;查找在左半区间进行,转步骤2;
3.2 若 target > arr[mid],则low = mid + 1;查找在右半区间进行,转步骤2;
3.3 若 target = arr[mid],则查找成功,返回 mid 值;
描述
请实现无重复数字的升序数组的二分查找
给定一个 元素升序的、无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标从 0 开始),否则返回 -1
具体做法:
step 1:从数组首尾开始,每次取中点值。
step 2:如果中间值等于目标即找到了,可返回下标,如果中点值大于目标,说明中点以后的都大于目标,因此目标在中点左半区间,如果中点值小于目标,则相反。
step 3:根据比较进入对应的区间,直到区间左右端相遇,意味着没有找到。
代码演示:
package binarysearch;
public class Test2 {
public static void main(String[] args) {
Test2 test2 = new Test2();
int[] arr=new int[]{-1,0,3,5,9,11};
int search = test2.search(arr, 9);
System.out.println(search);
}
public int search (int[] nums, int target) {
if(nums.length==0)
return -1;
//定义下标的左右边界【】闭区间
int left=0,right=nums.length-1;
//从数组首位开始,知道二者相遇
while(left<=right){ //如【1,1】是一个合法的区间
int mid = (left+right)/2;
if(nums[mid]==target){
return mid;
}
//目标值在左区间,mid-1
else if(target<nums[mid]){
right=mid-1;
}else//目标值在右区间,mid是加一
left=mid+1;
}
//没找到
return -1;
}
}
注意的细节:
1、为什么 while 循环的条件中是 <=,而不是 <?
答:因为初始化 right 的赋值是 nums.length - 1,即最后一个元素的索引,而不是 nums.length。
这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。
我们这个算法中使用的是前者 [left, right] 两端都闭的区间。这个区间其实就是每次进行搜索的区间。
2、为什么 left = mid + 1,right = mid - 1?我看有的代码是 right = mid 或者 left = mid,没有这些加加减减,到底怎么回事,怎么判断?
答:这也是二分查找的一个难点,不过只要你能理解前面的内容,就能够很容易判断。
刚才明确了「搜索区间」这个概念,而且本算法的搜索区间是两端都闭的,即 [left, right]。那么当我们发现索引 mid 不是要找的 target 时,下一步应该去搜索哪里呢?
当然是去搜索 [left, mid-1] 或者 [mid+1, right] 对不对?因为 mid 已经搜索过,应该从搜索区间中去除。
(版本二)左闭右开区间
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid;
}
return -1;
}
}
var search = function(nums, target) {
// right是数组最后一个数的下标+1,nums[right]不在查找范围内,是左闭右开区间
let mid, left = 0, right = nums.length;
// 当left=right时,由于nums[right]不在查找范围,所以不必包括此情况
while (left < right) {
// 位运算 + 防止大数溢出
mid = left + ((right - left) >> 1);
// 如果中间值大于目标值,中间值不应在下次查找的范围内,但中间值的前一个值应在;
// 由于right本来就不在查找范围内,所以将右边界更新为中间值,如果更新右边界为mid-1则将中间值的前一个值也踢出了下次寻找范围
if (nums[mid] > target) {
right = mid; // 去左区间寻找
} else if (nums[mid] < target) {
left = mid + 1; // 去右区间寻找
} else {
return mid;
}
}
return -1;
};