二分搜索,你真的懂吗?
二分搜索的前提是什么?有序,确实,我们平时从一组数据数据中查找某个数是否存在时,如果这个数组是无序的,那就从头到尾遍历一遍,或者先把数组排序再使用二分查找。其实使用二分不必一定要数组元素有序,只需要数组左边和右边可以区分,例如:
如图所示,数组的左端点为L,右端点为R。同时数组的红色部分满足一种性质,青色部分满足另外一种性质。
假如此时我要找红色部分的右端点。首先寻找数组的一半mid = (L+R)>>1;如果mid满足某个性质,则L=mid,自然R=mid-1;以上是模板,当发现R=mid-1时,为了避免出现越界的情况,在求mid时要加1,即mid = (L+R)>>1。
假如此时我要找蓝色的左端点。首先寻找数组的一半mid = (L+R)>>1;如果mid满足某个性质,则R=mid,自然L=mid+1;以上是模板。可能这样说有一点生硬,咱们用一个实际的例子来说明。
假如我要从数组[1,1,2,3,3,3,4,5]中确定数字3的首次出现和最后一次出现的位置。先上代码,再来解释。
public static int[] bin(int[] nums,int target){
int l=0,r=nums.length-1;
int mid=0;
while (l<r){
mid = (l+r)>>1;
if (nums[mid]>=target){
r=mid;
}else {
l=mid+1;
}
}
if (nums[l]!=target) return new int[]{-1,-1};
int[] res = new int[2];
res[0] = l;
l=0;r=nums.length-1;
while (l<r){
mid = l+r+1>>1;
if (nums[mid]<=target){
l=mid;
}else {
r=mid-1;//这里我们根据实际的应用场景判断出r=mid-1,因此要改变mid=(l+r+1)>>1
}
}
res[1] = l;
return res;
}
从上图我们可以看出我们要找的元素就是以目标元素3为界限,当我们需要找3的开始元素时,其实就是找右边元素的左边界;当我们的nums[mid]符合有百年元素的性质时,也就是nums[mid]>=3时,R=mid;否则,当nums[mid]<3时,L=mid+1;最终跳出循环的时候,L=R,此时的nums[L]一定时大于等3的第一个元素,所以判断一下nums[L]是否等于目标元素3,等于则说明数组中存在该元素,否则说明数组中压根不存在该元素。同理,当我们需要找3的结尾元素时,其实就是找左边元素的右边界。
这样解释,你看懂了吗?
同理,当我们需要找3的结尾元素时,其实就是找左边元素的右边界。
这样解释,你看懂了吗?