LeetCode Binary Search Summary 二分搜索法小結

 

二分查找法做爲一種常見的查找方法,將本來是線性時間提高到了對數時間範圍,大大縮短了搜索時間,具備很大的應用場景,而在 LeetCode 中,要運用二分搜索法來解的題目也有不少,可是實際上二分查找法的查找目標有不少種,並且在細節寫法也有一些變化。以前有網友留言但願博主能針對二分查找法的具體寫法作個總結,博主因爲以前一直很忙,一直拖着沒寫,爲了樹立博主言出必行的正面形象,不能再無限制的拖下去了,那麼今天就來作個了斷吧,總結寫起來~ (如下內容均爲博主本身的總結,並不權威,權當參考,歡迎各位大神們留言討論指正)html

根據查找的目標不一樣,博主將二分查找法主要分爲如下五類:面試

 

第一類: 需查找和目標值徹底相等的數數組

這是最簡單的一類,也是咱們最開始學二分查找法須要解決的問題,好比咱們有數組 [2, 4, 5, 6, 9],target = 6,那麼咱們能夠寫出二分查找法的代碼以下:函數

 

int find(vector<int>& nums, int target) {
    int left = 0, right = nums.size();
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) return mid;
        else if (nums[mid] < target) left = mid + 1;
        else right = mid;
    }
    return -1;
}

 

會返回3,也就是 target 的在數組中的位置。注意二分查找法的寫法並不惟一,主要能夠變更地方有四處:post

第一處是 right 的初始化,能夠寫成 nums.size() 或者 nums.size() - 1。url

第二處是 left 和 right 的關係,能夠寫成 left < right 或者 left <= right。spa

第三處是更新 right 的賦值,能夠寫成 right = mid 或者 right = mid - 1。指針

第四處是最後返回值,能夠返回 left,right,或 right - 1。code

可是這些不一樣的寫法並不能隨機的組合,像博主的那種寫法,若 right 初始化爲了 nums.size(),那麼就必須用 left < right,而最後的 right 的賦值必須用 right = mid。可是若是咱們 right 初始化爲 nums.size() - 1,那麼就必須用 left <= right,而且right的賦值要寫成 right = mid - 1,否則就會出錯。因此博主的建議是選擇一套本身喜歡的寫法,而且記住,實在不行就帶簡單的例子來一步一步執行,肯定正確的寫法也行。htm

第一類應用實例:

Intersection of Two Arrays

 

第二類: 查找第一個不小於目標值的數,可變形爲查找最後一個小於目標值的數

這是比較常見的一類,由於咱們要查找的目標值不必定會在數組中出現,也有多是跟目標值相等的數在數組中並不惟一,而是有多個,那麼這種狀況下 nums[mid] == target 這條判斷語句就沒有必要存在。好比在數組 [2, 4, 5, 6, 9] 中查找數字3,就會返回數字4的位置;在數組 [0, 1, 1, 1, 1] 中查找數字1,就會返回第一個數字1的位置。咱們可使用以下代碼:

 

int find(vector<int>& nums, int target) {
    int left = 0, right = nums.size();
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) left = mid + 1;
        else right = mid;
    }
    return right;
}

 

最後咱們須要返回的位置就是 right 指針指向的地方。在 C++ 的 STL 中有專門的查找第一個不小於目標值的數的函數 lower_bound,在博主的解法中也會時不時的用到這個函數。可是若是面試的時候人家不讓使用內置函數,那麼咱們只能老老實實寫上面這段二分查找的函數。

這一類能夠輕鬆的變形爲查找最後一個小於目標值的數,怎麼變呢。咱們已經找到了第一個不小於目標值的數,那麼再往前退一位,返回 right - 1,就是最後一個小於目標值的數。

第二類應用實例:

 
第二類變形應用: Valid Triangle Number
 

第三類: 查找第一個大於目標值的數,可變形爲查找最後一個不大於目標值的數

這一類也比較常見,尤爲是查找第一個大於目標值的數,在 C++ 的 STL 也有專門的函數 upper_bound,這裏跟上面的那種狀況的寫法上很類似,只須要添加一個等號,將以前的 nums[mid] < target 變成 nums[mid] <= target,就這一個小小的變化,其實直接就改變了搜索的方向,使得在數組中有不少跟目標值相同的數字存在的狀況下,返回最後一個相同的數字的下一個位置。好比在數組 [2, 4, 5, 6, 9] 中查找數字3,仍是返回數字4的位置,這跟上面那查找方式返回的結果相同,由於數字4在此數組中既是第一個不小於目標值3的數,也是第一個大於目標值3的數,因此 make sense;在數組 [0, 1, 1, 1, 1] 中查找數字1,就會返回座標5,經過對比返回的座標和數組的長度,咱們就知道是否存在這樣一個大於目標值的數。參見下面的代碼:

 

int find(vector<int>& nums, int target) {
    int left = 0, right = nums.size();
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] <= target) left = mid + 1;
        else right = mid;
    }
    return right;
}

 

這一類能夠輕鬆的變形爲查找最後一個不大於目標值的數,怎麼變呢。咱們已經找到了第一個大於目標值的數,那麼再往前退一位,返回 right - 1,就是最後一個不大於目標值的數。好比在數組 [0, 1, 1, 1, 1] 中查找數字1,就會返回最後一個數字1的位置4,這在有些狀況下是須要這麼作的。

第三類應用實例:

Kth Smallest Element in a Sorted Matrix

第三類變形應用示例:

Sqrt(x)

 

第四類: 用子函數看成判斷關係(一般由 mid 計算得出)

這是最令博主頭疼的一類,並且一般狀況下都很難。由於這裏在二分查找法重要的比較大小的地方使用到了子函數,並非以前三類中簡單的數字大小的比較,好比 Split Array Largest Sum 那道題中的解法一,就是根據是否能分割數組來肯定下一步搜索的範圍。相似的還有 Guess Number Higher or Lower 這道題,是根據給定函數 guess 的返回值狀況來肯定搜索的範圍。對於這類題目,博主也很無奈,遇到了只能自求多福了。

第四類應用實例:

Split Array Largest SumGuess Number Higher or LowerFind K Closest ElementsFind K-th Smallest Pair DistanceKth Smallest Number in Multiplication TableMaximum Average Subarray IIMinimize Max Distance to Gas StationSwim in Rising WaterKoko Eating BananasNth Magical Number

 

第五類: 其餘(一般 target 值不固定)

有些題目不屬於上述的四類,可是仍是須要用到二分搜索法,好比這道 Find Peak Element,求的是數組的局部峯值。因爲是求的峯值,須要跟相鄰的數字比較,那麼 target 就不是一個固定的值,並且這道題的必定要注意的是 right 的初始化,必定要是 nums.size() - 1,這是因爲算出了 mid 後,nums[mid] 要和 nums[mid+1] 比較,若是 right 初始化爲 nums.size() 的話,mid+1 可能會越界,從而不能找到正確的值,同時 while 循環的終止條件必須是 left < right,不能有等號。

相似的還有一道 H-Index II,這道題的 target 也不是一個固定值,而是 len-mid,這就很意思了,跟上面的 nums[mid+1] 有殊途同歸之妙,target 值都隨着 mid 值的變化而變化,這裏的right的初始化,必定要是 nums.size() - 1,而 while 循環的終止條件必須是 left <= right,這裏又必需要有等號,是否是很頭大 -.-!!!

其實仔細分析的話,能夠發現其實這跟第四類仍是比較類似,類似點是都很難 -.-!!!,第四類中雖然是用子函數來判斷關係,但大部分時候 mid 也會做爲一個參數帶入子函數進行計算,這樣實際上最終算出的值仍是受 mid 的影響,可是 right 卻能夠初始化爲數組長度,循環條件也能夠不帶等號,你們能夠對比區別一下~

第五類應用實例:

Find Peak Element

H-Index II

 

綜上所述,博主大體將二分搜索法的應用場景分紅了主要這五類,其中第二類和第三類還有各自的擴展。根據目前博主的經驗來看,第二類和第三類的應用場景最多,也是最重要的兩類。第一類,第四類,和第五類較少,其中第一類最簡單,第四類和第五類最難,遇到這類,博主也沒啥好建議,多多練習吧~

 

若是有寫的有遺漏或者錯誤的地方,請你們踊躍留言啊,共同進步哈~

 

LeetCode All in One 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索