文章目录
前言
博主是一名大二的学生,最近在学习Java,今天看到执梗大佬的一篇文章,感觉受益匪浅,因此写了一篇博客来总结学习。(推荐先看大佬的文章)
一、二分查找是什么?
二分查找又叫折半查找,它的算法思想非常简单,我们通过一个猜数字游戏来引入:
一天,聪明的小明正在和他的妹妹丽丽做游戏。小明对妹妹说,你在心里想一个0-100之间的数字,每次我猜一个数字,如果猜大了,你只要告诉我猜大了,如果猜小了你只需要告诉我猜小了,无论你想的是哪个数字,我总能在七次之内猜对。为何小明如此自信呢?让我们看看他猜数的过程
丽丽想的第一个数字是 71。小明:50 ->丽丽: 猜小了 -> 小明:75 ->丽丽:猜大了 -> 小明:73 -> 丽丽:猜大了 ->小明:72 -> 丽丽:猜大了 -> 小明:71 -> 丽丽:猜对了
我们看到,小明每次都是猜测候选集中间的数字,这样候选集大小每次减少一半,而
2
7
=
128
>
100
2^7=128>100
27=128>100,所以小明总能在七次之内猜出来!
这便是二分查找的基础思想,对于一个有序的数组
n
u
m
s
[
n
]
nums[n]
nums[n],查找的目标值为
t
a
r
g
e
t
target
target,我们每次只要对中间值
n
u
m
s
[
m
i
d
]
nums[mid]
nums[mid]与
t
a
r
g
e
t
target
target进行比较,判断
t
a
r
g
e
t
target
target所在的区间即可。
二
1.二分查找:查找一个数
代码如下:
public static int test(int[] nums,int k){ //升序数组nums[]
int left=0; //left是左边界
int right= nums.length-1; //right是右边界
while(left<=right){ //<=的目的是为了保证target
int mid=left+(right-left)/2; //防止溢出
if(k==nums[mid])
return mid;
else if(k<nums[mid])
right=mid-1; //已经比较过mid
else if(k>nums[mid])
left = mid + 1; //同上
}
return -1;
}
为什么一定要强调left<=right,我们去掉“=”举个反例
nums[3]={1,2,3};target=3;此时我们第一次 left=0, right=2, mid=1;
发现 t a r g e t > n u m s [ m i d ] target>nums[mid] target>nums[mid],因此根据程序执行 l e f t = m i d + 1 = 2 left=mid+1=2 left=mid+1=2,此时满足 l e f t = r i g t h left=rigth left=rigth循环结束,返回值为 -1 表示没有找到,可是 3 明明在数组中。由此可见,去掉等号相当于将候选集从 [ l e f t , r i g h t ] [left,right] [left,right]变成了 ( l e f t , r i g h t ) (left,right) (left,right)。
2.二分查找:第一次出现和最后一次出现的位置
通过上述努力,我们可以判断一个数字是否在数组里了,并且可以返回它的索引。然而如果数组中有多个
t
a
r
g
e
t
target
target并且我想让你返回第一个或者最后一个索引,又该怎么寻找呢,当然你可以一个一个的往左或者往右寻找,那我们二分查找的意义就没了。由此,我们对他进行修改,下面来看具体的代码
代码如下:
寻找第一次出现的下标
//寻找第一次出现的下标 搜索左边界
public static int testfirst(int[] nums,int k){
int left=0;
int right= nums.length-1;
while(left<=right){
int mid=left+(right-left)/2;
if(k==nums[mid]){ //只有这个if内容修改了
if(left==right) //不加这句话,会陷入死循(rigth=mid=left)
return left; //left和right一样
right=mid; //right=mid;
}
else if(k<nums[mid])
right=mid-1;
else if(k>nums[mid])
left = mid + 1;
}
return -1;
}
寻找最后一次出现的下标
public static int testlast(int[] nums,int k){
int left=0;
int right= nums.length-1;
while(left<=right){
int mid=(left+right+1)/2; //思考???
if(k==nums[mid]){
if(left==right)
return right;
left=mid;
}
else if(k<nums[mid])
right=mid-1;
else if(k>nums[mid])
left = mid + 1;
}
return -1;
}
搜索左边界的时候, r i g h t = m i d right=mid right=mid,那么搜索右边界的时候岂不是只需要写成 l e f t = m i d left=mid left=mid就可以了?结果发现程序死循环,没错这个傻子就是我。
可以看到我把
m
i
d
=
l
e
f
t
+
(
r
i
g
h
t
−
l
e
f
t
)
/
2
;
mid=left+(right-left)/2;
mid=left+(right−left)/2;修改成了
m
i
d
=
(
l
e
f
t
+
r
i
g
h
t
+
1
)
/
2
;
mid=(left+right+1)/2;
mid=(left+right+1)/2;
可能有的朋友要反问我了,难道左右边界不是对称的吗?为什么右边界要写成这样?
因为我们的
i
n
t
int
int型除法得到的是商。举个例子:
n
u
m
s
[
8
]
nums[8]
nums[8]数组里面放了8个数字1
l e f t = 0 , r i g h t = 7 left=0,right=7 left=0,right=7我们发现 m i d = 3 − > l e f t = 3 − > m i d = 5 − > l e f t = 6 > m i d = 6 − > l e f t = 6 mid=3 ->left=3->mid=5->left=6>mid=6->left=6 mid=3−>left=3−>mid=5−>left=6>mid=6−>left=6
我们发现 l e f t < r i g h t left<right left<right永远成立。程序陷入死循环!
时间复杂度: f ( n ) = O ( l o g N ) f(n)=O(log N) f(n)=O(logN)
很多时候题目里出现要求时间复杂度为
f
(
n
)
=
O
(
l
o
g
N
)
f(n)=O(log N)
f(n)=O(logN),其实即是对小伙伴的要求,也是一种提示,是否可以使用二分法求解呢?
以上就是今天分享内容,本文仅仅作为自己学习的总结,为同样在学习的小伙伴提供参考。