二分法查找法学习笔记总结

纵然是瞬间 提交于 2019-11-29 16:34:30

1 二分法学习笔记总结

参考

https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/
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/


1.1 取中位数索引的方法

  • 传统的方法 int mid = (left + right) /2 ,在 left 和 right 比较大的时候, 两者相加很可能超过 int 类的最大值,即发现整型溢出;
  • 改进 int mid = left + (right - left) /2
  • 最好的写法 int mid = (left + right) >>> 1

  • >>, 右移时,丢弃右边指定位数,左边补上符号位;
  • >>>, 无符号右移运算符,丢弃右边指定的位数,左边补上 0 。所以对负数右移,可以变成正数;

1.2 循环条件

  • while (left <= right),退出循环时,要考虑返回 left 和 right;

2 二分查找模板思想

2.1 循环条件

  • while (left < right), 在退出循环的时候,一定有 left == right,这时返回 left 或者 right 都可以;
  • 退出循环的时候还有一个数没有考虑,即退出循环之前索引 left 或者 索引 right 上的值?这点可以等到退出循环以后考虑,甚至有时不用考虑,就确定它是目标数;

2.2 左、右边界值

  • 如果左、右边界值不包括目标数,会出现错误;
  • 如果 left 和 right 表示的是数组的索引,要考虑“索引有效性的问题”,即 索引是否越界

2.3 中位数索引

  • 中位数先写 int mid = (left + right) >>> 1,然后根据分支的情况,再做调整;

当数组个数是偶数的时候:

  • int mid = left + (right -left) / 2, 得到左中位数的索引
  • int mid = left + (right -left + 1) / 2, 得到右中位数的索引

当数组个数是奇数的时候,二者都能得到最中间的元素

  • int mid = left + (right -left) / 2 等价于 int mid = (left + right) >>> 1
  • int mid = left + (right -left + 1) / 2 等价于 int mid = (left + right + 1) >>> 1

2.4 逻辑分支

  • 先写逻辑上最容易想到的分支,这个分支逻辑通常是排除中位数的逻辑;
  • 例如,求 x 的平方根时,如果一个数的平方小于或者等于 x, 那么这个数可能是也可能不是 x 的平方根;但是可以肯定的是,如果一个数的平方大于 x ,这个数肯定不是 x 的平方根;
  • 所以先写 “容易想到”的分支,排除中位数之后,通常另一个分支就不排除中位数,不必具体考虑另一个分支的逻辑的具体含义;

  • 在循环内只写 2 个分支,一个分支排除中位数,另一个分支不排除中位数,循环中不单独对中位数做判断;
  • “夹逼法”,没有必要在每一轮循环开始前单独判断当前中位数是否是目标元素,因此分支数少了一支,代码执行效率高
  • 可能出现以下 2 种模板
    在这里插入图片描述

在这里插入图片描述

2.5 左、右中位数的选取

  • 根据分支逻辑选择中位数的类型,选择的标准是避免出现死循环

当候选区间只剩下两个元素的时候,进入中位数是左边界的逻辑,由于左边界不收缩,下一次循环还选左中位数,左边界还不收缩,如此下去,进入了死循环。解决方案:使用右中位数;
在这里插入图片描述


当候选区间只剩下 2 个元素的时候,一旦进入中位数是右边界的逻辑,由于右边界不收缩,下一次循环还选右中位数,右边界还不收缩,如此下去,进入了死循环;解决方法:换成左中位数
在这里插入图片描述

2.6 退出循环的处理

  • 退出循环的时候,可能需要对“夹逼法”剩下的那个数单独做一次判断;
  • 如果能确定候选区间里一定有目标元素,则不需要这一步。例如:x 的平方根一定在 [0,x] 内,所以退出 while ( left < right) 后,不必单独判断 left 或者 right 是否符合;
  • 如果不确定候选区间里是否存在目标元素,则需要单独做一次判断。因为目标数可能不在数组中,当候选区间变成一个数的时候,需要单独判断这个数是否为目标数。

3 死循环代码测试

public static int testSqrt(int x) throws InterruptedException {
        if (x == 1 || x == 0) {
            return x;
        }

        int left = 1;
        int right = x / 2;

        while (left < right) {

            Thread.sleep(1000);

            System.out.println("left = " + left + ",right= " + right);

            // 在分支左区间不发生收缩时,中位数要选右中位数

            int mid = (left + right) >>> 1;
            System.out.println("mid = " + mid);

            int square = mid * mid;

            if (square > x) {
                System.out.println("进入 right = mid -1 分支");

                right = mid - 1;
            } else {
                System.out.println("进入 left=mid 分支");
                left = mid;
            }

            System.out.println();
        }

        return left;

    }

    public static void main(String[] args) throws InterruptedException {
        int res = testSqrt(9);
        System.out.println(res);
    }

在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!