二分法通常被用来在一个有序数组中找某个数是否存在,时间复杂度是O(logn)。
在一个有序数组中,找某个数是否存在
每一次都是从数组的中间开始找,具体过程可以参考图。
理顺了流程图,那代码就容易出来了,完整代码代码-二分法求数据是否存在
public static boolean findNumIsExitByBiSection(int[] arr,int target) {
if ( arr == null || arr.length == 0 ) {
return false;
}
int l = 0,r = arr.length;
int mid = 0;
while(l <= r) {
mid = l + (r-l)/2;
if ( arr[mid] > target) {
r = mid - 1;
}else if (arr[mid] < target) {
l = mid + 1;
}else {
return true;
}
}
return arr[mid] == target;
}
第一种用法想必大家都熟悉不过了,那二分法其实还有三种适用场景,是不是有点意外。今天的重点放在后面这三种,接下来为大家画图说明。
在一个有序数组中,找>=某个数最左侧的位置
话不多说,直接上图
先找一个数组中存在的数据
再找一个数组中不存在的数据
两个流程图中,都能顺利找到>=某个数的最最左侧。
这里和查找数据是否存在最大的区别在于,只要我的value >= target ,那么我就一直循环,直到L < R循环结束
那代码如何实现呢?完整代码代码-二分法求大于x的最左侧
public static int findNumNearLeftByBiSection(int[] arr,int target) {
if ( arr == null || arr.length == 0 ) {
return -1;
}
int l = 0,r = arr.length;
int mid = 0;
int index = -1;
while(l <= r) {
mid = l + (r-l)/2;
//mid 已经大于了target,左边还有没有大于target的,不清楚继续找,r向左移
if ( arr[mid] >= target) {
index = mid;
r = mid - 1;
}else {
l = mid + 1;
}
}
return index;
}
在一个有序数组中,找<=某个数最右侧的位置
那这个就和上面的功能相反,这里我们也画图说明下
核心代码如下所示,完整代码代码-二分法求小于x的最右侧
public int findNumNearrightByBiSection(int[] arr,ind target){
if(arr == null || arr.length = 0) {
return -1;
}
int l = 0,r = arr.length;
int index = -1;
int mid = 0;
while(l <= r) {
mid = l + (r-l)>>1;
// mid 已经小于了target,右边还有没有小于target的,不清楚继续找,l向右移
if (arr[mid] <= target) {
index = mid;
l = mid + 1;
}else{
// mid 已经大于target,右边不要了,r 向左移动
r = mid -1;
}
return index;
}
局部最小值问题
局部最小的定义是:
一个无序数组,任意两个相邻的数不等, 数组两端,arr[0] < arr[1],那么0位置数就是最小,arr[n-2] > arr[n-1],那么n-1位置数就是最小,而中间的数据arr[i]必须满足,arr[i-1] > arr[i] < arr[i+1],那么i位置的数就是局部最小。
问题:返回这个无序数组中任意一个局部最小值?
局部最小值在两端
局部最小值在中间
那对于局部最小值在中间的,我们的查找流程是怎样的呢?
情况1:mid中间值,比两侧都小
情况2:中间值比两侧都打,那么此时任意取哪一侧
情况3:mid的左边大,右边小,那么我们要找的就是小的,因为小的才更有可能存在最小值
情况4:mid的左边小,右边大,那么反过来,我们要在左边找局部最小
核心代码如下所示,完整代码代码-求局部最小值
public int findLocalMinimumByBiSection(int[] arr) {
if(arr == null || arr.length = 0) {
return -1;
}
if (arr.length == 1 || arr[0] < arr[1] ) {
return 0;
}
if (arr[arr.length-1] < arr[arr.length-2] {
return arr.length -1 ;
}
int left = 1;
int right = arr.length - 2;
int mid = 0;
while(left <= right) {
int mid = left + (right - left) >> 1;
if(arr[mid] > arr[mid + 1] ) {
left = mid + 1;
}else if(arr[mid] > arr[mid-1]) {
right = mid -1;
}else {
return mid;
}
return mid;
}
总结:
二分法只用于在有序数组中查找数据是否存在,似乎已经成为了我的思维定式。这篇文章对二分法其他的使用场景进行了扩充,主要有以下四种:
-
在一个有序数组中,找某个数是否存在
-
在一个有序数组中,找>=某个数最左侧的位置
-
在一个有序数组中,找<=某个数最右侧的位置
-
局部最小值问题
其中最后关于局部最小值的问题,也说明了一个问题,二分法不是只能用于有序数组的。只要能正确构建左右两侧的淘汰逻辑,就可以使用二分
实战案例
知道概念了,那刷题的时候如何使用,等我一一列举。
扫码关注公众号,获取文章密码,惊喜多多