认识二分法-你不知道的二分法

235 阅读4分钟

二分法通常被用来在一个有序数组中找某个数是否存在,时间复杂度是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;
}

总结:

二分法只用于在有序数组中查找数据是否存在,似乎已经成为了我的思维定式。这篇文章对二分法其他的使用场景进行了扩充,主要有以下四种:

  1. 在一个有序数组中,找某个数是否存在

  2. 在一个有序数组中,找>=某个数最左侧的位置

  3. 在一个有序数组中,找<=某个数最右侧的位置

  4. 局部最小值问题

其中最后关于局部最小值的问题,也说明了一个问题,二分法不是只能用于有序数组的。只要能正确构建左右两侧的淘汰逻辑,就可以使用二分

实战案例

知道概念了,那刷题的时候如何使用,等我一一列举。


扫码关注公众号,获取文章密码,惊喜多多

qrcode_for_gh_7d098dc4bd36_258.jpg