二分查找各种类型例题

普通二分查找

二分法中left + (right - left) /2 就和 (left + right) / 2 的结果相同,但是有效防⽌了 left 和right 太⼤直接相加导致溢出。

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;
}

1、为什么 while 循环的条件中是 <=,⽽不是 <?
答:因为初始化 right 的赋值是 nums.length - 1 ,即最后⼀个元素的索引,⽽不是 nums.length 。
这⼆者可能出现在不同功能的⼆分查找中,区别是:前者相当于两端都闭区间 [left, right] ,后者相当于左闭右开区间 [left, right) ,因为索引大小为 nums.length 是越界的。

我们这个算法中使⽤的是前者 [left, right] 两端都闭的区间。这个区间其实就是每次进⾏搜索的区间。
什么时候应该停⽌搜索呢?当然,找到了⽬标值的时候可以终⽌:
if(nums[mid] == target)
return mid;
但如果没找到,就需要 while 循环终⽌,然后返回 -1。那 while 循环什么时候应该终⽌?搜索区间为空的时候应该终⽌,意味着你没得找了,就等于没找到嘛。

while(left <= right) 的终⽌条件是 left == right + 1 ,写成区间的形式就是 [right + 1, right] ,或者带个具体的数字进去 [3, 2] ,可⻅这时候区间为空,因为没有数字既⼤于等于 3 ⼜⼩于等于 2 的吧。所以这时候while 循环终⽌是正确的,直接返回 -1 即可。
while(left < right) 的终⽌条件是 left == right ,写成区间的形式就是[left, right] ,或者带个具体的数字进去 [2, 2] ,这时候区间⾮空,还有⼀个数 2,但此时 while 循环终⽌了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。

此算法有什么缺陷?
答:⾄此,你应该已经掌握了该算法的所有细节,以及这样处理的原因。但是,这个算法存在局限性。
⽐如说给你有序数组 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;//注意这里
    while(left < right){
        int mid = (left + right)/2;
        if(nums[mid] == target) right = mid;
        else if(nums[mid] < target ) left = mid+1;
        else if(nums[mid] > target) right  = mid;
    }
   return nums[left] == target ? left : -1;
}
另一种写法:
     int left_bound(int[]nums,int target){
    int left = 0;
		int right = nums.length - 1;
		while (left <= right) {
			int mid = (right - left) / 2 + left;
			if (nums[mid] == target) {
				right = mid - 1;
			} else if (nums[mid] < target) {
				left = mid + 1;
			} else if (nums[mid] > target) {
				right = mid - 1;
			}
		}
		// 如果退出循环了,也就是left = right + 1
		// 判断下越界了不
		if (left >= nums.length || nums[left] != target) {
			return -1;
		}
		return left;

}

1、为什么 while 中是 < ⽽不是 <= ?
答:⽤相同的⽅法分析,因为 right = nums.length ⽽不是 nums.length -1 。因此每次循环的「搜索区间」是 [left, right) 左闭右开。
while(left < right) 终⽌的条件是 left == right ,此时搜索区间 [left,left) 为空,所以可以正确终⽌。

PS:这⾥先要说⼀个搜索左右边界和上⾯这个算法的⼀个区别,也是很多读者问的:刚才的 right 不是 nums.length - 1 吗,为啥这⾥⾮要写成nums.length 使得「搜索区间」变成左闭右开呢?
因为对于搜索左右侧边界的⼆分查找,这种写法⽐较普遍,我就拿这种写法举例了,保证你以后遇到这类代码可以理解。你⾮要⽤两端都闭的写法反⽽更简单,我会在后⾯写相关的代码,把三种⼆分搜索都⽤⼀种两端都闭的写法统⼀起来,你耐⼼往后看就⾏了

2.为什么该算法能够搜索左侧边界?
答:关键在于对于 nums[mid] == target 这种情况的处理:
if (nums[mid] == target)
right = mid;

可⻅,找到 target 时不要⽴即返回,⽽是缩⼩「搜索区间」的上界right ,在区间 [left, mid) 中继续搜索,即不断向左收缩,达到锁定左侧边界的⽬的。

查找第一个大于等于给定值的元素

public static int search(int[] nums, int val) {
int low = 0, high = nums.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
if (nums[mid] < val) {
low = mid + 1;
} else {
// 如果nums[mid]是第一个元素,或者nums[mid-1]小于val
// 说明nums[mid]就是第一个大于等于给定值的元素
if (mid == 0 || nums[mid - 1] < val) {
return mid;
}
high = mid - 1;
}
}
return -1;
}

或者这样:

public static int search(int[] nums, int val) {
int low = 0, high = nums.length - 1,index=0;
while (low <= high) {
int mid = (low + high) >>> 1;
if (nums[mid] < val) {
low = mid + 1;
} else {
// 如果nums[mid]是第一个元素,或者nums[mid-1]小于val
// 说明nums[mid]就是第一个大于等于给定值的元素
index=mid;
high = mid - 1;
}
}
return index;
}

查找最后一个小于等于给定值的元素

public class Code06_BSNearRight {

    //在arr上,找满足<=value的最右位置    O(logn)
    public static int nearestIndex(int[] arr,int value){
        int L=0;
        int R=arr.length-1;
        int index = -1;//记录最左边的对号
        while(L<=R){
            int mid=L+((R-L)>>1);
            //发现左侧有小于目标值的,抛弃左边,左边下标L=mid+1
            if(arr[mid]<=value){
                index=mid;
                L=mid+1;
            }else {
                R=mid-1;
            }
        }
        return index;
    }

找到小于等于目标值的最大位置

 public int binarySearch(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        if (nums[left] > target) {
            return -1;
        }
        while (left < right) {
            int mid = (right - left + 1) / 2 + left;
            if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid;
            }
        }
        return left;
    }


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值