深入理解二分的两种实现
Posted ACBingo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解二分的两种实现相关的知识,希望对你有一定的参考价值。
在日常面试中,经常遇到有的候选人甚至连二分都写不对。你可能会说,不就是个二分吗,有啥难的?其实,不要小看二分哦,能完全写对的真的不多。
首先我们先看最朴素的二分写法:
public int binarySearch(int[] a, int target) {
int left = 0;
int right = a.length;
while (left < right) {
int mid = ((right - left) >> 1) + left;
if (a[mid] == target) {
return mid;
} else if (a[mid] > target) {
right = mid;
} else {
left = mid + 1;
}
}
return -1;
}
对于上面这种二分实现,请大家思考以下几个问题:
right的初始化可否改成:right = a.length – 1;
while循环的条件应该是left < right 还是 left <= right
右半查询应该是left = mid + 1 还是 left = mid
左半查询应该是right = mid 还是 right = mid -1
首先我们先不管第一个问题,暂且认为我们就把right初始化为right = a.length;
那么来到第二个问题,为什么不能是<=号。因为我们right的取值是n,如果我们while退出条件是<=,那么当leftrightn的时候,是仍会进入到循环体的,然后循环体内访问数组的操作就会发生数组越界。
第三个问题,写成left = mid会有问题吗?会有。假设让left = k,而right = k + 1的时候,此时mid = (k + k + 1)/ 2,因为有整形向下取整的问题,算出来的mid仍然等于k。所以此时让left = mid的话,二分范围是没有发生缩小的,此时就会造成死循环。
第四个问题,写成right = mid – 1 会有问题吗?会有。假设让left = k,而right = k + 2的时候,此时mid = k + 1;如果此时mid > target,说明此时应该进行左半查询:right = mid – 1 = k,此时有left == right,然而又有我们的循环退出条件是while (left < right),所以就退出二分了,那么此时left == right 这种情况就漏查了。
理解了后三个问题,我们再回过头来看第一个问题。也就是说当你决定给right初始化为 n 还是 n – 1 的时候,后三个问题应该选哪个就已经确定下来了。那么,当right初始化为n-1的时候,二分又怎么写呢?
public int binarySearch(int[] a, int target) {
int left = 0;
int right = a.length - 1;
while (left <= right) {
int mid = ((right - left) >> 1) + left;
if (a[mid] == target) {
return mid;
} else if (a[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
ok,在这种写法下,我们继续看待上面几个问题。
第二个问题:while循环的条件应该是left < right 还是 left <= right?如果此时while循环条件是<,那么就会出现 left == n – 1 == right的时候,无法进入循环体,这个位置的数据就不会去查找了,所以有问题
第三个问题同上,不重复了
第四个问题:左半查询应该是right = mid 还是 right = mid -1?如果我们此时写出 right = mid。假设当left == right时,此时target仍然大于a[left] 与 a[right],所以走到了右半查找,但是right = mid,此时就会造成二分搜索的区间并没有变化,while进入了死循环。正确的是right = mid -1,使得 left <= right 条件不再成立,进而退出循环。
授之鱼不如授之以渔,二分靠死记硬背多痛苦,这四处细节一处都不能错。但是学会了以上分析的办法,大家在现场写二分的时候只需要多花一点点时间就能自己分析去每个细节应该如何确定。
以上是关于深入理解二分的两种实现的主要内容,如果未能解决你的问题,请参考以下文章