普通二分查找 + 查找边界的二分


二分的主要场景是:在有序数组中logn找到某个数字
扩展的有:找到这个数字的同时,要求下标尽可能小或者尽可能大(即找左侧边界、右侧边界)
那这个时候我们就要深入理解细节,才能写出相应限制下的二分算法。

主要关心的有:while括号的不等号是否要带上等号,mid是否应该+1,left的更新方式,right的更新方式,返回值的选取等等。

二分查找的框架

先来写一个二分查找的大概框架

int binarySearch(vector<int> nums, int target){
	int l = 0, r = ……, mid;
	while(……){
		mid = l + r >> 1;
		if(nums[mid] == target){
		 	……
		}else if(nums[mid] < target){
			left = ……
		}else if(nums[mid] > target){
			right = ……
		}
	}
	return ……;
}

尽量不要出现else,用else if把所有情况列出来,这样更清楚,然后再做合并化简

查找一个数

int binarySearch(vector<int> nums, int target){
	int l = 0, r = nums.size()- 1, mid;//区间是左闭右闭
	while(l <= r){//因为是左闭右闭所以当l>r才没有搜索空间需要退出,故这里l<=r均有搜索空间
		mid = l + r >> 1;
		if(nums[mid] == target){
		 	return mid;//找到了就好,没有要求边界,直接返回
		}else if(nums[mid] < target){
			left = mid + 1;
		}else if(nums[mid] > target){
			right = mid - 1;
		}
	}
	return -1;//没有找到,返回负一
}

这样算法是正确的,但是有一个问题,如果我加上了左右边界的限制怎么办?

举个有问题的例子
nums = [1,2,2,2,3],target = 2,此算法返回的索引是 2,也就是最中间的那个2,倘若我想要返回的是2最小的下标或者最大的下标,那这个算法只能往左或者往右线性跑,无法满足时间复杂度的要求,因此需要做出更改。

查找左侧边界的二分搜索

int left_bound_binarySearch(vector<int> nums, int target){
	if(nums.size() == 0){
		return -1;
	}
	int l = 0, r = nums.size(), mid; //搜索区间是左闭右开
	while(l < r) { //因为是左闭右开,所以当l>=r时没有搜索空间需要退出,故这里为l<r
		mid = l + r >> 1;
		if(nums[mid] == target){
			right = mid;//实现往左边界靠拢
		}else if(nums[mid] < target){
			left = mid + 1;
		}else if(nums[mid] > target){
			right = mid; //这里不-1了,因为right是开的,不-1才正确。
		}
	}
	if(left == nums.size()){//比所有数字都大,此时下标不合法
		return -1;
	}
	return nums[left] == target ? left : -1;//没有找到,返回-1
}

其实这里返回的下标有一个含义,譬如说你返回了下标3,意味着前面有3个数字(下标0~2)比你这个数字小,
那么当我们的返回下标为nums.size()时,意味着比所有数字都大,也就是找不到这个数字,那么此时要返回-1
如果我们返回下标为0时,有可能是nums[0]就是我们求的数字,也可能是0个数字比这个数字小,这个数字有可能比所有数字小,所以我们最后还要判断一下。

查找右边界的二分搜索

int left_bound_binarySearch(vector<int> nums, int target){
	if(nums.size() == 0){
		return -1;
	}
	int l = 0, r = nums.size(), mid; //搜索区间是左闭右开
	while(l < r) { //因为是左闭右开,所以当l>=r时没有搜索空间需要退出,故这里为l<r
		mid = l + r >> 1;
		if(nums[mid] == target){
			left = mid + 1;//注意这里,可以这样想mid = left - 1,mid是合法答案,那最后就返回left - 1
		}else if(nums[mid] < target){
			left = mid + 1;
		}else if(nums[mid] > target){
			right = mid; //这里不-1了,因为right是开的,不-1才正确。
		}
	}
	if(left == 0){ //left - 1不合法
		return -1;
	}
	return nums[left - 1] == target ? (lefet - 1) : -1;//没有找到,返回-1
}

注意

计算 mid 时需要技巧防止溢出,建议写成: mid = left + (right - left) / 2

总结

无论如何,left的更新肯定都是要left=mid+1的,否则就会存在left=3 rgiht=4,然后一直死循环的情况。

参考来源

我觉得这篇博客写的很好:详解二分查找算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值