二分法查找法學習筆記總結

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);
    }

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章