<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          關(guān)于二分查找,我有話說

          共 954字,需瀏覽 2分鐘

           ·

          2021-10-09 03:44



          前言


          寫這篇文章前,我看了很多講二分查找的文章,包括公眾號和知乎的,在我看來,講得都比較差,說幾點原因:
          1、要么只講最簡單的情況
          2、要么講了稍復(fù)雜的情況,但是一會兒閉區(qū)間一會兒開區(qū)間,一會兒加一減一,一會兒又不加減,一會兒還要打個補丁
          二分法細節(jié)確實比較多,但是真正理解之后,是可以有一個通用模板的,根本不需要考慮到底是開區(qū)間還是閉區(qū)間,加一還是減一還是不加減,更不需要打補丁。

          本文只講最基礎(chǔ)的情況,但是我認(rèn)為最基礎(chǔ)的情況,至少也包括五種,而我看到的目前市面上的文章,很少講全了五種。

          我們先假設(shè)數(shù)組是升序排列,再給你一個數(shù)target。
          題目總共可能有五種情況,這五種情況分別是:
          1、最基礎(chǔ)版,只要找到target就行,返回下標(biāo),找不到返回-1
          2、進階版一,找到等于target的左邊界(數(shù)組中可能有多個數(shù)都等于target),找不到返回-1
          3、進階版二,找到等于target的右邊界(數(shù)組中可能有多個數(shù)都等于target),找不到返回-1
          4、進階版三,找到小于target的最大值,找不到返回-1
          5、進階版四,找到大于target的最小值,找不到返回-1

          總體原則,進階版一和進階版二應(yīng)該盡量用同一種方法解決,而不需要考慮太多不一樣的細節(jié)。而在我這篇文章中,進階版一二三四都是同一個套路,各位看好。


          最基礎(chǔ)版二分法


          首先你需要知道最基礎(chǔ)版應(yīng)該怎么寫,二分法的思想其實很簡單,就是先取中間的數(shù),然后和target比較,如果小了就往右找,大了就往左找。然后再取中間數(shù),如此循環(huán),直到中間沒有數(shù)了。

          思路我相信大家都能理解,但是在寫法上,實際上還有一個雙指針的思想,也就是我們需要left和right兩個指針,然后不斷調(diào)整兩個指針的位置,最終得到答案。

          廢話不多說,我們看一下代碼。

          public static int commonBinarySearch(int[] arr, int target) {
          int left = 0, right = arr.length - 1;
          while(left <= right) {
          int mid = left + (right - left) / 2;
          if(arr[mid] == target) {
          return mid;
          }
          if(arr[mid] < target) {
          left = mid + 1;
          } else {
          right = mid - 1;
          }
          }
          return -1;
          }

          代碼其實很簡單我就不多解釋了。

          我相信可能有同學(xué)看過其他版本,比如所謂的開區(qū)間版本,但是我告訴大家,沒有必要去看去記這么多版本,你只要會一種就能行走天下,而我建議大家就寫上面這種

          這種也叫閉區(qū)間版本,每一個數(shù)都會搜索到,需要注意的幾個點
          1、right初始是arr.length-1,很好理解,因為這是數(shù)組右邊界下標(biāo)
          2、while里面是left<=right,也好理解,因為我們每個數(shù)都要搜索,當(dāng)left=right時,我們當(dāng)然要進里面再判斷一下
          3、區(qū)間范圍縮小時,left=mid+1或者right=mid-1,也好理解,因為arr[mid]已經(jīng)不是我們要找的數(shù),所以范圍需要加一或者減一

          之所以要推薦閉區(qū)間版本,是因為它最符合我們的直觀邏輯,而且它是對稱的,是把left和right一視同仁,而不會像有的版本left需要加一,但是right卻不需要減一。


          進階版


          有了基礎(chǔ)模板,我們怎么去做進階版呢?大家可以先思考一下第二個問題。

          說實話,如果是第一次碰到這個問題,還真不一定能找到最優(yōu)解,第一個直觀感受可能是我找到mid后,在往左一個一個地看,看看哪里是邊界,但是這樣,時間復(fù)雜度可能退化成o(n)。

          其實還是應(yīng)該用二分的思想找,關(guān)鍵在于arr[mid]==target時候的處理。

          我們來看一下我的解法。

          public static int firstBinarySearch(int[] arr, int target) {
          int left = 0, right = arr.length - 1;
          int res = -1;
          while(left <= right) {
          int mid = left + (right - left) / 2;
          if(arr[mid] == target) {
          res = mid;
          right = mid - 1;
          } else if(arr[mid] < target) {
          left = mid + 1;
          } else {
          right = mid - 1;
          }
          }
          return res;
          }

          這里的一個技巧就是當(dāng)arr[mid]==target時,因為要找左邊界,我們把right=mid-1,也就是改變右邊界從而縮小范圍。這時候其實存在兩種情況,要么答案已經(jīng)是res(左邊界),[left,right]里面已經(jīng)沒有答案,while再也不會更新res,最終返回res是正確的,要么res還不是最終答案,那么答案就會在[left,right]里面,而在while中,我們總能找到它從而更新res,最終返回res也是正確的。

          這里我們額外定義了一個res表示結(jié)果,這一步是整個解法的精髓。相對于很多其他教程在考慮到底是返回left還是left+1還是right還是right-1,直接定義一個res要好理解得多。

          這里也要注意幾點:
          1、res一定是符合條件的,比如在這里,我們只有當(dāng)arr[mid]==target的時候,我們才會更新res。
          2、res會隨著搜索范圍減小越來越接近答案,當(dāng)搜索結(jié)束時,res就是答案。
          3、如果一次都沒有更新res,說明整個數(shù)組里沒有滿足條件的數(shù),res應(yīng)該為-1,也就是初始值。

          理解了第二問你再做第三問,我相信你直接看代碼就行,都不需要我解釋。

          public static int lastBinarySearch(int[] arr, int target) {
          int left = 0, right = arr.length - 1;
          int res = -1;
          while(left <= right) {
          int mid = left + (right - left) / 2;
          if(arr[mid] == target) {
          res = mid;
          left = mid + 1;
          } else if(arr[mid] < target) {
          left = mid + 1;
          } else {
          right = mid - 1;
          }
          }
          return res;
          }

          同樣,你做第四問和第五問,只需要考慮什么時候更新res,因為第四問要找的數(shù)是小于target的,所以應(yīng)該在arr[mid]target時更新res。

          第四問:

          public static int lowerBiggest(int[] arr, int target) {
          int left = 0, right = arr.length - 1;
          int res = -1;
          while(left <= right) {
          int mid = left + (right - left) / 2;
          if(arr[mid] < target) {
          res = mid;
          left = mid + 1;
          } else {
          right = mid - 1;
          }
          }
          return res;
          }

          第五問:

          public static int higherSmallest(int[] arr, int target) {
          int left = 0, right = arr.length - 1;
          int res = -1;
          while(left <= right) {
          int mid = left + (right - left) / 2;
          if(arr[mid] <= target) {
          left = mid + 1;
          } else {
          res = mid;
          right = mid - 1;
          }
          }
          return res;
          }


          題外話


          通過這個問題,我也說說題外話。我記得曾經(jīng)看過linus的一個視頻(視頻可以點擊閱讀原文打開),其中一個點我印象很深刻,他舉了一個單鏈表刪除節(jié)點的例子,他說所有的教科書都會告訴我們要分情況,刪除頭節(jié)點和刪除一個中間節(jié)點是不一樣的邏輯。但是他覺得不是,他說要從另外一個角度看,這兩種情況可以統(tǒng)一,而一個邏輯統(tǒng)一的代碼才是好代碼。


          練習(xí)題


          學(xué)習(xí)完二分查找,大家可以去做一下以下的習(xí)題:
          leetcode704 二分查找
          leetcode34 在排序數(shù)組中查找第一個和最后一個元素
          leetcode35 搜索插入位置
          leetcode33 搜索旋轉(zhuǎn)排序數(shù)組
          leetcode162 尋找峰值


          還不會的話關(guān)注我,后續(xù)講解。



          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲一区二区电影网 | 操美女逼网站 | 影音先锋无码资源 | www.高清无码 | 成人性爱在线观看 |