二分查找的變形

原文鏈接:https://blog.csdn.net/zxzxzx0119/article/details/82670761#t4

二分查找的總結

  • 普通的二分查找

  • 普通二分查找的另一種寫法

  • 第一個 = key的,不存在返回 -1

  • 第一個>= key

  • 第一個> key

  • 第一個...的總結

  • 最後一個= key 的 ,不存在返回- 1

  • 最後一個<= key

  • 最後一個 < key

  • 最後一個...的總結


普通的二分查找

最普通的寫法:

  • 範圍在[L,R]閉區間中,L = 0R = arr.length - 1

  • 注意循環條件爲 L <= R ,而不是L < R

這裏寫圖片描述

    static int bs1(int[] arr,int key){
        int L = 0,R = arr.length - 1; //在[L,R]範圍內尋找key
        int mid;
        while( L <= R){
            mid = L + (R - L) / 2;
            if(arr[mid] == key)
                return mid;
            if(arr[mid] > key)
                R = mid - 1;// key 在 [L,mid-1]內
            else
                L = mid + 1;
        }
        return -1;
    }

    普通二分查找的另一種寫法

    首先說明,這個和上面的二分查找是完全一樣的,只不過我們定義的區間不同而已:

    • 上面的二分查找是在[L,R]的閉區間中查找,而這個二分查找是在[L,R)的左閉右開區間查找;

    • 所以此時的循環條件是L < R ,因爲R本來是一個不可到達的地方,我們定義爲了開區間,所以R是一個不會考慮的數,所以我們循環條件是L < R

    • 同理,當arr[mid] > key的時候,不是R = mid - 1,因爲我們定義的是開區間,所以R = mid ,因爲不會考慮arr[mid]這個數;

        //和上面的完全一樣,只是一開始R不是arr.length-1 而是arr.length
        static int bs2(int[] arr,int key){
            int L = 0, R = arr.length; //注意這裏R = arr.length 所以在[L,R)開區間中找
            int mid;
            while( L < R){ //注意這裏 不是 L <= R
                mid = L + (R - L)/2;
                if(arr[mid] == key)
                    return mid;
                if(arr[mid] > key)
                    R = mid; // 在[L,mid)中找
                else
                    L = mid + 1;
            }
            return -1;
        }
    

      上面的兩種方式一般還是第一種方式用的多一點。


      第一個 = key 的,不存在返回 -1

      這個和之前的不同是:

      • 數組中可能有重複的key,我們要找的是第一個key的位置;

      • 和普通二分查找法不同的是在我們要R = mid - 1前的判斷條件不是arr[mid] > key,而是arr[mid] >= key

      • 爲什麼是上面那樣,其實直觀上理解,我們要找的是第一個,那我們去左邊找的時候不僅僅arr[mid] > key就去左邊找,等於我也要去找,因爲我要最左邊的等於的;

      • 最後我們要判斷L是否越界(L 有可能等於arr.length),而且最後arr[L]是否等於要找的key

      • 如果arr[L]不等於key,說明沒有這個元素,返回-1

      舉個例子:

      這裏寫圖片描述

      	/**查找第一個與key相等的元素的下標, 如果不存在返回-1 */
          static int firstEqual(int[] arr,int key){
              int L = 0, R = arr.length - 1; //在[L,R]查找第一個>=key的
              int mid;
              while( L <= R){
                  mid = L + (R - L)/2;
                  if(arr[mid] >= key)
                      R = mid - 1;
                  else
                      L = mid + 1;
              }
              if(L < arr.length && arr[L] == key)
                  return L;
              return -1;
          }
      

        第一個>= key

        這個和上面那個尋找第一個等於key的唯一的區別就是:

        • 最後我們不需要判斷(L < arr.length && arr[L] == key),因爲如果不存在key的話,我們返回第一個> key的元素即可;

        • 注意這裏沒有判斷越界(L < arr.length),因爲如果整個數組都比key要小,就會返回arr.length的大小;

        	/**查找第一個大於等於key的元素的下標*/
            static int firstLargeEqual(int[] arr,int key){
                int L = 0, R = arr.length - 1;
                int mid;
                while( L <= R){
                    mid = L + (R - L) / 2;
                    if(arr[mid] >= key)
                        R = mid - 1;
                    else
                        L = mid + 1;
                }
                return L;
            }
        

          第一個 > key

          這個和上兩個的不同在於:

          • if(arr[mid] >= key)改成了if(arr[mid] > key),因爲我們不是要尋找 = key的;

          • 看似和普通二分法很像,但是我們在循環中沒有判斷if(arr[mid] == key)就返回mid(因爲要尋找的不是等於key的),而是在最後返回了L

          舉個例子:

          這裏寫圖片描述

              /**查找第一個大於key的元素的下標 */
              static int firstLarge(int[] arr,int key){
                  int L = 0,R = arr.length - 1;
                  int mid;
                  while(L <= R){
                      mid = L + (R - L) / 2;
                      if(arr[mid] > key)
                          R = mid - 1;
                      else
                          L = mid + 1;
                  }
                  return L;
              }
          

            第一個...的總結

            上面寫了三個第一個.....的程序,可以發現一些共同點 ,也可以總結一下它們微妙的區別:

            • 最後返回的都是L

            • 如果是尋找第一個等於key的,是if( arr[mid] >= key) R = mid - 1,且最後要判斷L的合法以及是否存在key

            • 如果是尋找第一個大於等於key的,也是if(arr[mid] >= key) R = mid - 1,但是最後直接返回L

            • 如果是尋找第一個大於key的,則判斷條件是if(arr[mid] > key) R = mid - 1,最後返回L


            最後一個 = key 的 ,不存在返回 - 1

            和尋找第一個 = key的很類似,不過是方向的不同而已:

            • 數組中有可能有重複的key,我們要查找的是最後一個 = key的位置,不存在返回-1

            • 爲了更加的直觀的理解,和尋找第一個…的形成對比,這裏是當arr[mid] <= key的時候,我們要去右邊查找(L = mid + 1),同樣是直觀的理解,因爲我們是要去找到最後一個 = key的,所以不僅僅是arr[mid] < key要去左邊尋找,等於key的時候也要去左邊尋找;

            • 和第一個…不同的是,我們返回的都是R

            • 同時我們也要判斷R的下標的合法性,以及最後的arr[R]是否等於key,如果不等於就返回-1

            舉個例子:

            這裏寫圖片描述

                /**查找最後一個與key相等的元素的下標, 如果沒有返回-1*/
                static int lastEqual(int[] arr,int key){
                    int L = 0, R = arr.length - 1;
                    int mid;
                    while( L <= R){
                        mid = L + (R - L)/2;
                        if(arr[mid] <= key)
                            L = mid + 1;
                        else
                            R = mid - 1;
                    }
                    if(R >= 0 && arr[R] == key)
                        return R;
                    return -1;
                }
            

              最後一個<= key

              這個和上面那個尋找最後一個等於key的唯一的區別就是:

              • 最後我們不需要判斷 (R >= 0 && arr[R] == key),因爲如果不存在key的話,我們返回最後一個 < key的元素即可;

              • 注意這裏沒有判斷越界(R >= 0),因爲如果整個數組都比key要大,數組最左邊的更左邊一個(也就是-1);

                  /**查找最後一個小於等於key的元素的下標 */
                  static int lastSmallEqual(int[] arr,int key){
                      int L = 0, R = arr.length - 1;
                      int mid;
                      while( L <= R){
                          mid = L + (R - L) / 2;
                          if(arr[mid] <= key)
                              L = mid + 1;
                          else
                              R = mid - 1;
                      }
                      return R;
                  }
              

                最後一個 < key

                這個和上面兩個不同的是:

                • 和上面的程序唯一不同的就是arr[mid] <= key改成了 arr[mid] < key,因爲我們要尋找的不是= key的;

                • 注意這三個最後一個的都是先對L的操作L = mid + 1,然後在else 中進行對R的操作;

                這裏寫圖片描述

                    /**查找最後一個小於key的元素的下標*/
                    static int lastSmall(int[] arr,int key){
                        int L = 0, R = arr.length - 1;
                        int mid;
                        while(L <= R){
                            mid = L + (R - L) / 2;
                            if(arr[mid] < key)
                                L = mid + 1;
                            else
                                R = mid - 1;
                        }
                        return R;
                    }
                

                  最後一個...的總結

                  上面三個都是求最後一個.....的,也進行一下總結:

                  • 最後返回的都是R

                  • 第一個if判斷條件(不管是arr[mid] <= key還是arr[mid] < key) ,都是L的操作,也就是去右邊尋找;

                  • 如果是尋找最後一個 等於key的, if(arr[mid] <= key) L = mid + 1; 不過最後要判斷R的合法性以及是否存在key

                  • 如果是尋找最後一個 小於等於 key的,也是if(arr[mid] <= key) L = mid + 1;不過最後直接返回R

                  • 如果是尋找最後一個 小於 key的,則判斷條件是 if(arr[mid] < key) L = mid + 1 ,最後返回R


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