[数据结构与算法]1.二分查找

第一次写文章,如果有说的不对的地方欢迎大佬们指正

基础的二分法

前提:有序数组
形式:二分法一般有两种写法,分别对应不同的区间定义。

区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。

一种是左闭右闭,即[left, right],另一种是左闭右开,即[left, right)

第一种写法:[left, right],即target是在这个闭区间内。

首先,while的循环条件必须是left <= right,因为只有left = right的时候,区间的右端点(i.e. right)才能被考虑到。因为在left < right的时候,(left + right) >> 1 < right,即mid < right,所以这时永远不可能考虑到right这个右端点。只有当left = right的时候, mid = right,我们才考虑到了右端点,所以这个等于的情况是有意义的,必须取等号。

其次,循环内判断的时候if(nums[mid] > target),也就是说明target值在[left, mid)区间内,由于我们考虑的是[left, right]这个闭区间,所以此时right应该置为mid - 1

这种写法最后终止时right = left - 1;

第二种写法:[left, right)

首先,while循环条件是left < right,因为left = right的情况对我们没有意义,我们不考虑right这个右端点。

其次,right一开始必须初始化为数组长度n而不是n - 1。

最后,if(nums[mid] > target),right应该更新为 mid,因为寻找区间是左闭右开区间,而nums[mid]不等于target,所以right更新为mid。

这种写法最后终止时left = right。

二分法的变式

要对二分法进行变式,关键是注意循环时不同if情况下指针的移动和循环最终终止时left和right的关系。
以leetcode题目34. 在排序数组中查找元素的第一个和最后一个位置为例:

这个题目中target有重复的值,所以主要修改两个基础写法最终定位到的target的位置。
这里参考了leetcode用户jys_std的评论代码。我们可以将查找第一个位置理解为查找大于等于target的第一个位置,而查找最后一个位置就是查找大于等于target + 1的第一个位置,这样就实现了统一。
我们知道基础的二分法是查找一个单独元素的位置,那么如何修改才能让它实现查找大于等于给定元素的第一个位置呢?
这里以第二种写法为例(左闭右开)。

这是基础二分法的样例java代码:

public int search(int[] nums, int target) {
        int left = 0, right = nums.length;
        while(left < right){
            int mid = (left + right) >> 1;
            if(nums[mid] < target) left = mid + 1;
            if(nums[mid] > target) right = mid;
            if(nums[mid] == target) return mid;
        }
        return -1;
    }

再给出此题的变式代码:

//找>=target的第一个
    public int search(int[] nums,int target){
        int l = 0, r = nums.length;
        while(l < r){
            int mid = (r + l) >> 1;
            if(nums[mid] >= target)
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }

上面提到了二分法的变式关键是注意循环时不同if情况下指针的移动和循环最终终止时left和right的关系

首先,在nums[mid] = target的时候,我们不能return,因为会有多个target,那么此时我们要找的是最左边的target,而我们不知道这个是不是最左边,但是可以确认的是右区间(mid, right]不用考虑了,而只要考虑[left, mid)这个区间。所以此时我们将right置为mid.

然后考虑nums[mid] > target的情况,此时和第一种情况一样,也是把right置为mid。

接着我们看nums[mid] < target的情况,此时target肯定在(mid, right)中,所以我们将left置为mid + 1;

最后我们关注循环终止时的情况,也就是left = right。此时指针会停在最左边的target的位置,因为基于上面的条件判断,nums[mid]等于target的时候,我们是将right左移,考虑左区间。最后因为left = right,我们返回任意一个就行。

当然此题还要考虑target值超出nums数组界限的情况:

public int[] searchRange(int[] nums, int target) {
        int l = search(nums,target);
        int r = search(nums,target + 1);
        if(l == nums.length || nums[l] != target)
            return new int[]{-1, -1};
        return new int[]{l, r - 1};
    }

l == nums.length对应target大于nums最大值的情况,nums[l] != target对应target小雨nums最小值的情况。

如果想练练手还可以去做做69. x 的平方根

参考:

  1. https://www.programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html#%E6%80%9D%E8%B7%AF
  2. https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/zai-pai-xu-shu-zu-zhong-cha-zhao-yuan-su-de-di-3-4/

你可能感兴趣的:(数据结构与算法java)