Back
Featured image of post SearchRange经典二分问题

SearchRange经典二分问题

BinarySearch upper/lower bound

二分查找

二分查找是一种效率较高的方法(logN) , 它的要求是线性表中元素是有序的

我们常见的写法有:

public int binarySearch(int[] nums , int target){
    int left = 0 , right = nums.length - 1;
    while(left <= right){
        //left > right则退出,此时搜索区间为[left , right]
        int mid = left + (right - left) / 2;//(left + right) / 2
        if (nums[mid] == target){
            return mid;
        }else if (nums[mid] > target){
            right = mid - 1;
        }else if (nums[mid] < target){
            left = mid + 1;
        }
    }
    return -1;
}

另一种写法:

public int binarySearch(int[] nums , int target){
    int left = 0 , right = nums.length;
    while(left < right){//difference
        //left == right则退出,此时搜索区间为[left , right)
        int mid = left + (right - left) / 2;
        if (nums[mid] == target){
            return mid;
        }else if (nums[mid] > target){
            right = mid;
        }else if (nums[mid] < target){
            left = mid + 1;
        }
    }
    return -1;
}

从两种写法中,我们可以得知有两个不同:循环条件的退出以及搜索区间,这是二分查找的易错点。

边界问题没处理好导致死循环,搜索区间没处理好则把结果去除了。


二分查找的另一种用法,搜索左右边界

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

通常我是把左右边界分开写,因为我怕逻辑出错导致WA,这里我用的是[Left , Right]去寻找左右边界。

而我们需要思考在什么时候更新下一个搜索区间,才可以逼近左/右边界

  1. 寻找左边界
    • nums[mid] > target , 此时说明未找到target,缩小区间
    • nums[mid] == target , 此时说明找到target,更新right ,但没有确定是不是边界
  2. 寻找右边界
    • 同理,我们在nums[mid] == target的时候更新left

第一种写法[left , right]

public int[] searchRange(int[] nums, int target) {
    if (nums.length == 0) return new int[]{-1, -1};
    int lower = lowerBound(nums, target);
    int upper = upperBound(nums, target);
    return new int[]{lower, upper};
}

private int lowerBound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] >= target) {//1.
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    if (left >= nums.length || nums[left] != target) {//检查元素是否符合
        return -1;
    }
    return left;
}

private int upperBound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] <= target) {//2.
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    if (right < 0 || nums[right] != target) {//检查元素是否符合
        return -1;
    }
    return right;
}

第二种写法[left , right)

public int[] searchRange(int[] nums, int target) {
    if (nums.length == 0) return new int[]{-1, -1};
    int upper = upper(nums, target);
    int lower = lower(nums, target);
    return new int[]{lower, upper};
}

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

private int upper(int[] nums, int target) {
    int left = 0, right = nums.length;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] <= target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    if (left == 0 || nums[left - 1] != target) return -1;
    return left - 1;//return right
}

合二为一写法(Leetcode官解)

详情请见在排序数组中查找元素的第一个和最后一个位置

Welcome to the world of Minezeratul