【算法】1、讲一下不会出错的二分查找思路(包含进阶)

198 阅读4分钟

题目

35. 搜索插入位置

难度:简单

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

  • 请必须使用时间复杂度为 O(log n) 的算法。
  • nums 为 无重复元素 的 升序 排列数组

整体思路

  1. 建模,划分蓝红区域;

  2. 设定lr的初始值,即l = -1,r = nums.length

  3. 设定循环条件,即l + 1 != r,抑或者题目要求的值(比如找不到就返回-1);

  4. 确定返回的l还是r

  5. 套用模板。

具体实现及细节

  1. 题目要求 “ 返回目标值索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置 ” ,表示我们需要返回的是 第一次“>= target”的索引值 。 因此,我们划分 < target”为蓝区,划分 >= target”为红区

  2. 设定 l=-1,r=nums.length,这样l永远处于蓝区的位置,r永远处于红区的位置(尤其当目标值索引为0或者nums.length时)。

  3. 设定循环条件l + 1 != r,防止死循环。 当l+1=r,意味着lr都处于红蓝边界,循环结束; 当l+2=r,意味着lr之间隔着一个数,于是再次循环一次; 当l+3=r,意味着lr之间隔着两个数,再次循环,结果为l+1=r或者l+2=r。 这样,也就保证了永远不会陷入死循环。

  4. 由题目要求,即“>=target”,我们需要返回红区的索引值,因此我们返回的是r

  5. 为什么是“l = m”以及“r = m ,而不是“l = m + 1”和“r = m - 1”呢? 由初始值l=-1r=nums.length,且l+1!=r(需要进入循环),可得:中间值m的最小值为(-1 + 1)/2 = 0,最大值为(nums.length-2 + nums.length)/2 = nums.length-1。 则m的取值范围为[0,nums.length-1],刚好对应整个数组。 当然,“l = m + 1”和“r = m - 1”在特定的逻辑里面也是可行的。

  6. 至于取中间值,为什么推荐用l + (r - l)/2,因为 (l + r)/2可能会内存溢出。即整型的字节大小有所限制,当lr很大时,可能会内存溢出,而(r - l)就不会。

 class Solution {
     public int searchInsert(int[] nums, int target) {
 ​
         int l = -1, r = nums.length;
         while(l + 1 != r){
             int m = l + (r - l)/2;
             if(target == nums[m]){
                 return m;
             }else if(target > nums[m]){
                 l = m;
             }else if(target < nums[m]){
                 r = m;
             }
         }
         return r;
     }
 }

>>:右移。该数为正,则高位补0,若为负数,则高位补1。

>>>:无符号右移。高位补0。

image.png



进阶题目

34. 在排序数组中查找元素的第一个和最后一个位置

难度:中等

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

解题思路

题目要求时间复杂度O(logn),这不就是妥妥的二分查找嘛。

这道题的难点在于边界问题,解决了边界问题,事情就好办了。

题目要求返回一个两个元素的数组,第一个元素为第一次==target的下标,第二个元素为最后一次==target的下标,否则返回[-1,-1]

我们可以分两步走,分别找出“左端值”和“右端值”。

“左端值”:确定红蓝边界为<target>=target。为什么=在右边呢?因为如果有多个相等目标数,我们需要返回最左边,即我们需要把区间往左靠,而>target情况下区间会往左靠。下面的右端值也是同样的道理。

可以知道,左端值有且只有在>=target,因此我们让==target时返回下标,否则返回-1(即找不到相等数)。需要注意的是我们会多次循环,直到找到最左下标(相等数);

“右端值”:确定红蓝边界为<=target>target。可以知道,右端值有且只有在<=target,因此我们让==target时返回下标,否则返回-1(即找不到相等数)。需要注意的是我们会多次循环,直到找到最右下标(相等数)。

代码

class Solution {
    public static int[] searchRange(int[] nums, int target) {
        return new int[]{search(nums,target,0),search(nums,target,1)};
    }

    public static int search(int[] nums,int target,int index){
        int l = -1, r = nums.length;
        //假设为-1,即找不到相等数
        int res = -1;
        while(l + 1 != r){
            //防止内存溢出
            int mid = l + ((r - l) >> 1);
            if (index == 0){
                //“左端值”
                if(nums[mid] < target){
                    l = mid;
                } else{
                    r = mid;
                    if(nums[mid] == target){
                        res = r;
                    }
                }
            } else {
                //“右端值”
                if(nums[mid] > target){
                    r = mid;
                } else{
                    l = mid;
                    if(nums[mid] == target){
                        res = l;
                    }
                }
            }
        }
        return res;
    }
}

image.png