二分法总结-上下边界确定-指针移动规则

绪论


二分法是用于查找有序序列中某个元素的常用方法,时间复杂度为log(n)

写过二分法的人就知道,二分法好用但是也经常让人头疼,因其边界往往容易出错,而且在刷力扣看题解的时候,一会儿while(left < right)一会儿while(left <= right),头都搞晕了,因此特地来学习总结一番

最容易出错的地方:

  1. while循环中到底是 小于 还是 小于等于
  2. 左右边界left和right,到底是+1 呢,还是要-1呢

这两个问题的本质是一样的,取决于你如何定义左右区间,可以分为两种:

边界开闭的确定

左闭右开

定义左边界left=0,右边界right=arr.length,由于数组的索引范围为0到arr.length-1,right=arr.length这个索引是越界的,所以你相当于定义了一个左闭右开的区间,[left,right)

所以你想想,是while(left < right)还是while(left <= right),如果说是后者,也就是left和right相等循环也能执行,那么当left=right=arr.length时也能进入循环,那数组不就越界了?

因此,当你定义的区间为左闭右开时,while里面一定是小于

第一个问题解决了,第二个问题我们进入代码说明,talk is cheap, show me the code

现在要从一个有序数组arr[n]中查找某个元素,代码如下:

public int binarySearch(int[] arr, int target){
    int left = 0, right = arr.length;
    while(left < right){
      // 等同于(left+right)/2,但是当left和right较大时两者相加容易发生溢出
      int mid = left + (right-left) / 2;

      if(arr[mid] == target){
           // 找到了target就直接返回mid
           return mid;
      }else if(arr[mid] > target){
           // 如果当前值大于target,而数组是升序的,说明target在数组的左半边[left,mid-1],但是由于区间定义是左闭右开,因此数组左半边写成[left,mid),所以right=mid
           right = mid;
      }else {
           // 最后,如果当前值小于target,则说明target在数组的右半边[mid+1,right),因此left直接等于mid+1就好了
           left = mid + 1;
      }
    }
  // 找不到就返回-1
      return -1;
}

左闭右闭

思路和上面差不多,只不过是定义的区间变了,因此我在这里简要说明一下

定义左边界left=0,右边界right=arr.length-1,这个时候左右边界均在arr索引范围内,因此是左闭右闭的,[left,right]

因此是while(left <= right)即便是到了right边界,也不会发生数组溢出

这里我不单独贴出左闭右闭的代码,而是将两个写法的代码进行对比

到这里,基本上就可以搞懂边界问题了

强烈建议你使用左闭右闭这样的区间定义,也就是while(left <= right),不容易出错

上下边界问题

没有重复元素的就是上面的写法,这里着重讲一下最常用的有重复元素的序列

那我习惯左闭右闭的区间定义,这里用左闭右闭

找有重复元素的target又可以分为两种,比如arr[1,2,2,2,3],我要找2,那你是想找最左边的2还是最右边的2呢?可以称之为下边界上边界

上边界

// 下边界,也就是数组中第一次出现target的位置
public int binarySearchLowwer(){
    int left = 0;
    int right = arr.legth - 1;
    int pos = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] >= target) {
            // 中间的值要是大于target肯定往左区间找,如果中间值等于target呢
            // 因为我们是找下边界,所以也是往左区间搜索,因此这两种情况可以合并
            // 中间值大于等于target都往左区间[left,mid-1]查找
            pos = mid;  // pos会被不断的往左更新,直到为下边界
            right = mid - 1;
        }
        else {
            // 这个简单,中间的数小于target,往右子区间[mid+1,right]查找
            left = mid + 1;
        }
    }
    return pos;
}

下边界

// 上边界,也就是数组中最后一次出现target的位置
public int binarySearchUpper(){
    int left = 0;
    int right = arr.legth - 1;
    int pos = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] > target) {
            // 中间的值要是大于target肯定往左区间找,如果中间值等于target呢
            // 因为我们是找上边界,所以肯定往又区间找,所以放到else情况里面
            right = mid - 1;
        }
        else {
            // 中间的数小于等于target,往右子区间[mid+1,right]查找
            pos = mid;  // pos会被不断的往左更新,直到为下边界
            left = mid + 1;
        }
    }
    return pos;
}

可以观察到,上边界和下边界只有两处代码不同

  • 下边界arr[mid]大于等于target,上边界arr[mid]大于target
  • pos的位置不一样

参考

  1. https://zhuanlan.zhihu.com/p/395506019
  2. https://leetcode-cn.com/problems/binary-search/solution/er-fen-cha-zhao-de-xun-huan-bu-bian-liang-zhi-yao-/
posted @ 2022-03-14 09:55  Garrett_Wale  阅读(383)  评论(0编辑  收藏  举报