你真的了解二分查找吗?

250 阅读3分钟

【北京】 IT技术人员面对面试、跳槽、升职等问题,如何快速成长,获得大厂入门资格和升职加薪的筹码?与大厂技术大牛面对面交流,解答你的疑惑。《从职场小白到技术总监成长之路:我的职场焦虑与救赎》活动链接:码客

恭喜fpx,新王登基,lpl*b 我们是冠军

原文链接:segmentfault.com/a/119000002…

二分查找算法解析

基本框架:

int binarySearch(int[] nums, int target) {
    int left = 0, right = ...;

    while(...) {
        int mid = (right + left) / 2;
        if (nums[mid] == target) {
            ...
        } else if (nums[mid] < target) {
            left = ...
        } else if (nums[mid] > target) {
            right = ...
        }
    }
    return ...;
}
一、寻找一个数(基本的二分搜索)

这个场景是最简单的,肯能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1。

int binarySearch(int[] nums, int target) {
    int left = 0; 
    int right = nums.length - 1; // 注意
    while(left <= right) {
        int mid = (right + left) / 2;
        if(nums[mid] == target)
            return mid; 
        else if (nums[mid] < target)
            left = mid + 1; // 注意
        else if (nums[mid] > target)
            right = mid - 1; // 注意
        }
return -1;
}

采用<=的原因: 因为右边界取的是nums.length-1,如果是<则取不到最后一个边界,在执行left=mid+1时有可能越界,mid在left<right时是<mid的,在left=right时是等于right的,此时+1越界
while(left < right) 的终止条件是 left == right,在上一次循环中可能修改了其中一个值,导致这个值没有办法访问

上面这种方法是找到一个满足的就终止return,不一定是第一个

为什么 left = mid + 1,right = mid - 1?我看有的代码是 right = mid 或者 left = mid: 因为 mid 已经搜索过,应该从搜索区间中去除。

此算法有什么缺陷? 无法定位元素第一个或者最后一个。你也许会说,找到一个 target,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分查找对数级的复杂度了。

二、寻找左侧边界的二分搜索
int[] searchRange(int[] nums, int target) {
        int i = 0;
        int l = 0;int r = nums.length-1;
        while(l<=r){
            int mid = l + r >>1;
            if(nums[mid]>target){
                r = mid - 1;
            }else if(nums[mid]<target){
                l = mid + 1;
            }else{
                i = mid;
                r = mid - 1;
            }
        }
}

也可以写成:

int[] searchRange(int[] nums, int target) {
        int i = 0;
        int l = 0;int r = nums.length-1;
        while(l<=r){
            int mid = l + r >>1;
            if(nums[mid]>=target){
                r = mid - 1;
                if(nums[mid]==target){
                    i = mid;
                }
            }else{
                l = mid + 1;
            }
        }
}

相比找到一个数,这种形式我们不能在找到一个数后停止,还需继续缩小区间到左边,直到找到最左边的那个元素

也就是下面这段,当相等时,不直接return,而是保存当前值后缩小right区间到mid-1

else if(nums[mid]==target){
    i = mid;
    r = mid - 1;
}
三、寻找右侧边界的二分查找
int[] searchRange(int[] nums, int target) {
        int i = 0;
        int l = 0;int r = nums.length-1;
        while(l<=r){
            int mid = l + r >>1;
            if(nums[mid]>target){
                r = mid - 1;
            }else if(nums[mid]<target){
                l = mid + 1;
            }else{
                i = mid;
                l = mid + 1;
            }
        }
}

也可以写成:

int[] searchRange(int[] nums, int target) {
        int i = 0;
        int l = 0;int r = nums.length-1;
        while(l<=r){
            int mid = l + r >>1;
            if(nums[mid]<=target){
                l = mid + 1;
                if(nums[mid]==target){
                    i = mid;
                }
            }else{
                r = mid - 1;
            }
        }
}

相比找到一个数,这种形式我们不能在找到一个数后停止,还需继续缩小区间到右边,直到找到最右边的那个元素

也就是下面这段,当相等时,不直接return,而是保存当前值后缩小left区间到mid+1

else if(nums[mid]==target){
    i = mid;
    l = mid + 1;
}
小优化技巧
  • mid计算使用<<1代替/2
  • 防止溢出:当l增大时,l+r会很大,可能溢出,可以使用l+((r-l)>>1)

恰当使用,在不同需求下使用不同的二分查找

(想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)