「算法」二分查找:一道题带你领悟二分查找的精髓!

个人主页:Ice_Sugar_7
所属专栏:算法详解
欢迎点赞收藏加关注哦!

二分查找

  • 确定左端点
    • 调整 left 和 right
    • 细节处理
  • 确定右端点
    • 调整 left 和 right
    • 细节处理
  • 模板

直接上题:在排序数组中查找元素的第一个和最后一个位置

确定左端点

我们记左、右指针为 left 和 right,中点为 mid,左端点为 target
划分区间:target 左边为一个区间,target 和 target 右边为另一个区间。这样左区间就都小于 target,右区间就都是大于或等于 target

「算法」二分查找:一道题带你领悟二分查找的精髓!_第1张图片

调整 left 和 right

每次循环就是求出中点处的值 x,让 x 和 target 比较

  • 如果 x < target,那我们就要调整 left,让 left = mid + 1
  • 而如果是 x >= target,注意,这里把相等的情况也包括进来了,因为当中点值和 target 相等时,不一定是在左端点,所以都要移动 right
    right 的移动也很讲究,不能让 right = mid - 1,因为如果 mid 刚好就在 target 处,那 right 就会走到左区间了。所以要让 right = mid
    「算法」二分查找:一道题带你领悟二分查找的精髓!_第2张图片

细节处理

  1. 求中点的位置

在上篇文章中,我们讲了两个求中点的公式,分别可求左、右中点(当元素个数为偶数时)

我们以区间内只剩下 left 和 right 这两个元素的情况来分析(在下文分析循环终止条件时,我们可以推出最后一次循环时只剩下两个元素)

如果 mid 为右中点,并且中点值大于或等于 target,那就会死循环
「算法」二分查找:一道题带你领悟二分查找的精髓!_第3张图片
所以我们要用求左中点的公式:mid = left + (right - left) / 2


  1. 循环结束条件
    当 left >= right 时,就可以跳出循环了,注意 left == right 时不能进入循环

下面解释一下原因:

①如果[left,right]之间有 target,就是下图的情况:
「算法」二分查找:一道题带你领悟二分查找的精髓!_第4张图片
根据上面对 left 和 right 移动路径的分析,我们可以知道:right 只能在右区间内移动,无法走到左区间;而 left 就不一样了,如果 left 已经走到左区间最后一个位置,那么它最终刚好可以到 target 的位置(因为 left = mid + 1,mid 会和 left 重合,left + 1后就到 target 处)

也就是说,left 和 right 最终相遇的地方就在 target 处,此时没必要进循环了

如果[left,right]中的值全都比 target 大,这种情况下 right 就会不断往左走,直到和最左端的 left 相遇;反之,若全比 target 小,则 left 会一直走到 right 处

如果在 left == right 时还进入循环,并且 x >= target,那又会死循环了(因为 right 一直等于 mid)
所以正确的做法是:不进循环,判断 nums[mid] 是否等于 target,如果不相等那就说明数组中没有这个数


确定右端点

解决左端点之后,右端点也就很好处理了,现在的图示如下:
「算法」二分查找:一道题带你领悟二分查找的精髓!_第5张图片

调整 left 和 right

  • 如果 x <= target,就让 left = mid
  • 如果 x > target,则要让 right = mid-1

细节处理

  • 求中点的位置
    仿照上面的分析方式,可以推出要用求右中点的公式:mid = left + (right - left+1) / 2

其实什么时候要用哪个中点公式不用死记硬背,分析 mid 分别落在两个区间时 left 和 right 需要如何移动之后,放在最后一次循环中分析,看 mid 在哪个中点的时候可能会死循环,就可以推出要用哪个公式了


这道题代码如下:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int left = 0,right = nums.length - 1;
        int[] ret = new int[2];
        ret[0] = ret[1] = -1;
        if(nums.length == 0) return ret;  //排除数组中没有元素的情况
        
        //查找左端点
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else if(nums[mid] >= target) right = mid;
        }
        if(nums[left] == target) ret[0] = left;
        else return ret;
        
        right = nums.length - 1;  //查找右端点前先重置一下 right 的下标,left 可以不用重置
        //查找右端点
        while(left < right) {
            int mid = left + (right - left +1) / 2;
            if(nums[mid] <= target) left = mid;
            else if(nums[mid] > target) right = mid - 1;
        }
        ret[1] = left;
        return ret;
    }
}

模板

由这道题的代码,我们可以总结出以下两个模板:
「算法」二分查找:一道题带你领悟二分查找的精髓!_第6张图片
这两个模板分别是查找某个区间左端点和右端点的,要记忆的话,只需记住 mid 的公式:求左端点时就用左中点公式;反之则用右中点公式
下面的 left 和 right 根据题目分析即可

你可能感兴趣的:(算法详解,算法)