Leetcode分类解析:二分查找

这篇博客详细解析了LeetCode中二分查找的应用,包括原始二分查找、旋转排序数组、二维搜索等场景。文章通过举例和证明,阐述了二分查找的关键点和解题思路,并提供了代码模板。此外,还探讨了在处理旋转数组、重复元素和二维矩阵时二分查找的变化及其应对策略。
摘要由CSDN通过智能技术生成

Leetcode分类解析:二分查找


1.原始二分查找


1.1 典型例题

35-Search Insert Position (Medium): Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order. You may assume no duplicates in the array.
Here are few examples.
[1,3,5,6], 5 → 2
[1,3,5,6], 2 → 1
[1,3,5,6], 7 → 4
[1,3,5,6], 0 → 0

首先来看一道最经典的二分查找题,考察的就是二分查找的原始实现。因为这道题比较经典,所以下面源码做了大量注释。说是注释,其实是断言,用来在后面说明这段代码的正确性。特别注意一点,就是那个看似奇怪的mid计算方式,为什么不是(low+high)/2呢?做了很久Leetcode我都没注意到,看到有些答案这样写的还以为是多此一举,直到做到某一道题发生溢出,真是井底之蛙!这样写就是因为当low和high特别大时相加会发生Integer溢出,要么都转成long做完运算再转回来,但是下面这种写法更加方便,low+(high-low)/2,我们变相去求两者差的一半,由加法变成了减法。具体请看Google的这篇Blog。现在再看这种写法是不是感觉非常漂亮!

    public int searchInsert(int[] nums, int target) {
        if (nums.length == 0) {
            return 0;
        }

        // MustBe(0,n-1)
        int low = 0, high = nums.length - 1;

        // MustBe(low,high): (1) Initialization: invariant holds
        while (low <= high) {

            // MustBe(low,high) and low <= high
            int mid = low + (high - low) / 2;

            // MustBe(low,high) and low <= mid <= high
            if (nums[mid] < target) {

                // MustBe(low,high) and num[mid] < target <= num[high]
                // MustBe(mid+1,high)
                low = mid + 1;

                // MustBe(low,high): (2) Preservation: invariant holds
            } else if (nums[mid] > target) {

                // MustBe(low,high) and num[low] <= target < num[mid]
                // MustBe(low,mid-1)
                high = mid - 1;

                // MustBe(low,high)
            } else {
                return mid;
            }
        } // (3) Termination: range shrinks, so it must terminate

        // low > high, so it was low = high = mid (it's impossible to get low > high if low < high)
        // 1) target < num[mid], low=mid, high=mid-1, low is the insert position
        // 2) target > num[mid], high=mid, low=mid+1, low is the insert position too!
        return low;
    }

1.2 正确性证明

一直觉得Binary Search是非常好的考察正确性证明思路的一类题。因为它对区间……

下面就以这道题的原始Binary Search为例看一下为什么我们的代码是正确的,后面的题不会再这样繁琐的证明了。通过这道题了解思路和技巧,后面的题我们只要把握住关键点就可以了。

首先我们要搜索target的位置,所以用low和high组成的区间表示搜索范围,不变量Invariant就是:MustBe(low,high)表示target一定存在于区间[low,high],否则它不存在于nums数组。接下来就是运用循环迭代不断缩小这个区间,最重要的是在这个过程中保持Invariant始终为真,最终我们得到的就一定是正确的结果:

  1. 初始化(Initialization):初始时,MustBe(0,nums.length-1)包含了整个数组,按照MustBe的定义来说target要么存在于整个数组要么不存在,这肯定为真。于是Invariant初始状态没问题!
  2. 维护(Preservation):对于这道题很容易,如果target在前一半,那么MustBe(low,mid-1),所以更新high=mid-1就能维持Invariant为真了。反之,更新low=mid+1就行了。
  3. 终止(Termination):能够清楚看出在循环过程中,low和high区间是在不断shrink。

有了以上三条,当循环终止时low > high并且MustBe(low,high)为真,于是我们就能知道target不存在。其实个人感觉:MustBe(low,high)应该叫做CanBe(low,high)更贴切,因为target可能不存在于数组中。所以最后CanBe(low,high)并且low > high,使得CanBe变成了MustNotBe。但也许CanBe语气比较弱,不像断言?后面我们会看到153-Find Minimum in Rotated Sorted Array找Minimum最小值,这道题的Invariant叫MustBe才比较合适!


1.3 循环终止前的样子

但这道题还没完,可以说最关键的一点来了:要返回插入位置而不是-1就完事了。这就要求我们仔细分析一下循环停止前那一刻是什么样子。因为low < high不可能直接跳到low > high,low = high - 1是倒数第二轮的情况,而最后一轮一定是low = high。于是就有low = mid = high,如果target > nums[mid],那么会导致low = mid + 1,low就是插入位置。而如果target < nums[mid],那么会导致high = mid - 1,low同样是插入位置。所以不断最后一轮是什么情况,low一定是插入位置


1.4 解题关键点

这道题的代码非常标准,可以作为下面各道题Solution的模板。做下面各题时如果赶时间,比如面试时,可以先把上面的代码骨架写出来,再思考各个关键点。那Binary Search类型题都还有哪些变化,要把握住哪些关键点呢?在开始各个击破之前,先总结一下:

以二分查找为蓝本,这一类查找类型题还是能玩出不少变化的,例如最关键的几点有:

  1. low和high初始值:大部分都是数组的范围从0到N-1,个别像278-First Bad Version和374-Guess Number Higher or Lower是从1到N。
  2. 循环结束条件
    2.1 如果我们要找的target一定存在的话,那用low < high,最后low = high导致循环结束,low位置就是我们要找的数字了。这种一定存在的问题有:69-Sqrt、153-Find Minimum in Rotated Sorted Array、162-Find Peak Element、278-First Bad Version、374-Guess Number Higher or Lower。
    2.2 如果target不一定存在的话,就要用low <= high,最终low大于high导致循环退出,说明target不存在。
  3. 区间缩减
    3.1 首先是如何确定
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值