二分查找细节处理详细说明,防止bug必备

总的来说,二分查找思想是简单的,都知道它是那么一回事。但是一到去实际编程,总会冒出几个bug出来。二分查找的细节真的堪比玄学。
典型的就比如下面这几个细节:
1.整型溢出
2. mid要加一还是减一还是什么也不做;
3. while的判断是left < right 还是left <= rigth;

做为典型小白吗,头疼了这么久,今天还是来彻底解决下。本篇博文主要参考这个力克题解而做的一个笔记总结:
二分查找细节详解,顺便赋诗一首

下面进入正题:


1. 二分查找的基本框架

首先二分查找的框架基本就是这个样子,记住它!

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

    while(...) {
    
    
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
    
    
            ...
        } else if (nums[mid] < target) {
    
    
            left = ...
        } else if (nums[mid] > target) {
    
    
            right = ...
        }
    }
    return ...;
}

2.防止整型溢出

注意观察会发现:
mid = left + (right - left) / 2;
而不直接是:
mid = (left+right)/2
这里是为了防止了 left 和 right 太大直接相加导致溢出

3.二分法在有序数组中的简单应用

力克链接:二分查找

题目描述
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。


代码:
int binarySearch(int[] nums, int target) {
    
    
    int left = 0; 
    int right = nums.length - 1; 

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

注意1—left <= rigth

这里while的判断是left <= rigth;而用left<rigth就会出错。


分析:
此算法中每次进行搜索的区间是[left,right];
循环结束的条件有两种情况:

  1. nums[mid] == target 找到了目标元素
  2. 搜索区间为空

要搜索区间为空,那当然要求left > right;一般我们写循环时都要仔细思考临界条件。这里我们同样思考下极端情况,
如果二分搜索的达到最后,恰好是 : right =tagert
那么如果当left = right - 1时就退出循环,那么也就无法正确查找啦!

注意2—left = mid + 1,right = mid - 1

  • 首先为什么这里要加一,减一?
    因为我们上一步已经查找过了nums[mid],所以就可以加一减一啦!

  • 必须要这样吗?
    表面上看,这里如果不加一减一,好像就是多了一个数字,也不会影响最终结果。但是我以亲身的教训发现,这里还真的必须要加一减一,否则就会陷入死循环。
    当left = rigth -1时,是不是(left+right) /2就等于left,如果left 直接等于mid,是不是就无限循环下去了!

仔细斟酌斟酌,如果还没懂就自己模拟下面这一组数:
在数组[1,2]中寻找3,你会发现死循环了;

4.二分查找的进阶

上面这个经典的二分查找算法有局限性:
比如有序数组 nums = [1,2,2,2,3],target 为 2,此算法返回的索引是 2。但是如果想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是无法处理的

力克链接


题目描述:

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 你的算法时间复杂度必须是 O(log n) 级别。如果数组中不存在目标值,返回 [-1, -1]。

示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

对于这个题我就直接套用了上面的二分查找:

先找到一个目标值的下标,然后向下搜寻它的左边界,向上搜寻它的右边界。
这样的话,直接避免了再次书写二分查找的繁琐细节处理,而且时空效率也非常高。 *主要我不想在去仔细分析接下来的细节了,tnl;就用这个方法偷个懒吧!*

代码如下:

class Solution {
    
    
    public int[] searchRange(int[] nums, int target) {
    
    
        int[] ans = new int[2];
        if(nums == null || nums.length == 0)
        {
    
    
            ans[0]=ans[1]=-1;
            return ans;
        }
        int left=0,right = nums.length-1;
        int index = 0;    
        int mid = 0,flag =0,len=nums.length;
        while(left<=right)
        {
    
    
            mid = left + (right-left) / 2;
            if(target < nums[mid])
            {
    
    
                right = mid -1;
            }
            else if(target > nums[mid])
            {
    
    
                left = mid + 1;
            }
            else
            {
    
    
                index = mid;
                flag = 1;
                break;
            }
        }
       if(flag == 1)
       {
    
    
        ans[0] = index;
        //向下搜寻它的最小左边界
        for( ;ans[0]>=0 && nums[ans[0]] == target  ;ans[0]--);
        ans[0]++;
        
        //向下搜寻它的最大右边界
        ans[1] = index;
        for(;ans[1]<len && nums[ans[1]] == target  ;ans[1]++);
        ans[1]--;

       }else{
    
    
           ans[0]=-1;
           ans[1]=-1;
       }
        return ans;
    }
}

当然如果你想不畏困难,直视二分的更高一级细节处理,看下面的链接吧!
https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/

希望以后的二分查找能顺利写出代码来吧 ^ . ^

猜你喜欢

转载自blog.csdn.net/qq_45768060/article/details/106024283
今日推荐