二分查找總結

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

根據查找的目標不同,博主將二分查找法主要分為以下五類:

?

第一類: 需查找和目標值完全相等的數

這是最簡單的一類,也是我們最開始學二分查找法需要解決的問題,比如我們有數組[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 - 1;}return -1;
}

會返回3,也就是target的在數組中的位置。注意二分查找法的寫法并不唯一,主要可以變動地方有四處:

第一處是right的初始化,可以寫成 nums.size() 或者 nums.size() - 1

第二處是left和right的關系,可以寫成 left < right 或者 left <= right

第三處是更新right的賦值,可以寫成 right = mid 或者 right = mid - 1

第四處是最后返回值,可以返回left,right,或right - 1

但是這些不同的寫法并不能隨機的組合,像博主的那種寫法,若right初始化為了nums.size(),那么就必須用left < right,而最后的right的賦值必須用 right = mid。但是如果我們right初始化為 nums.size() - 1,那么就必須用 left <= right,并且right的賦值要寫成 right = mid - 1,不然就會出錯。所以博主的建議是選擇一套自己喜歡的寫法,并且記住,實在不行就帶簡單的例子來一步一步執行,確定正確的寫法也行。

第一類應用實例:

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,就是最后一個小于目標值的數。

第二類應用實例:

Heaters,?Arranging Coins,?Valid Perfect Square,Max Sum of Rectangle No Larger Than K,Russian Doll Envelopes

?

第二類變形應用: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)

?

第四類: 用子函數當作判斷關系

這是最令博主頭疼的一類,而且通常情況下都很難。因為這里在二分查找法重要的比較大小的地方使用到了子函數,并不是之前三類中簡單的數字大小的比較,比如Split Array Largest Sum那道題中的解法一,就是根據是否能分割數組來確定下一步搜索的范圍。類似的還有Guess Number Higher or Lower這道題,是根據給定函數guess的返回值情況來確定搜索的范圍。對于這類題目,博主也很無奈,遇到了只能自求多福了。

第四類應用實例:

Split Array Largest Sum,?Guess Number Higher or Lower,Find K Closest Elements,Find K-th Smallest Pair Distance,Kth Smallest Number in Multiplication Table,Maximum Average Subarray II,Minimize Max Distance to Gas Station,Swim in Rising Water,Koko Eating Bananas

?

第五類: 其他

有些題目不屬于上述的四類,但是還是需要用到二分搜索法,比如這道?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 Binary Search Summary 二分搜索法小結

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/385351.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/385351.shtml
英文地址,請注明出處:http://en.pswp.cn/news/385351.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

管道的讀寫行為

使用管道需要注意以下4種特殊情況&#xff08;默認都是阻塞I/O操作&#xff0c;沒有設置O_NONBLOCK標志&#xff09;&#xff1a; 1. 如果所有指向管道寫端的文件描述符都關閉了&#xff08;管道寫端引用計數為0&#xff09;&#xff0c;而仍然有進程從管道的讀端讀數據&#…

【C++ Primer | 08】課后習題答案

文章目錄練習8.13練習8.13 include <iostream> #include <sstream> #include <fstream> #include <string> #include <vector> using namespace std;struct PersonInfo {string name;vector<string> phones; };bool valid(const string&…

管道緩沖區大小

可以使用ulimit –a 命令來查看當前系統中創建管道文件所對應的內核緩沖區大小。通常為&#xff1a;pipe size 4K&#xff0c;即一個頁面大小。也可以使用fpathconf函數來查看&#xff1a; #include <unistd.h> long fpathconf(int fd, int name); 當需要查看管道的大…

FIFO(命名管道)

FIFO常被稱為命名管道&#xff0c;以區分管道(pipe)。管道(pipe)只能用于“有血緣關系”的進程間。但通過FIFO&#xff0c;不相關的進程也能交換數據。FIFO是Linux基礎文件類型中的一種&#xff08;p,管道文件&#xff09;。但FIFO文件在磁盤上沒有數據塊&#xff0c;僅僅用來標…

文件進程間通信

使用文件也可以完成IPC&#xff0c;理論依據是&#xff0c;fork后&#xff0c;父子進程共享文件描述符。也就共享打開的文件。 //父子進程共享打開的文件。借助文件進行進程間通信&#xff08;可先打開文件&#xff0c;再創建子進程&#xff09; #include <unistd.h> #…

mmap內存映射、system V共享內存和Posix共享內存

linux內核支持多種共享內存方式&#xff0c;如mmap內存映射&#xff0c;Posix共享內存&#xff0c;以system V共享內存。當內核空間和用戶空間存在大量數據交互時&#xff0c;共享內存映射就成了這種情況下的不二選擇。它能夠最大限度的降低內核空間和用戶空間之間的數據拷貝&a…

mmap、munmap函數

#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); int munmap(void *addr, size_t length); void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); 返回&#xff1a;成功&…

mmap和munmap對文件進行操作(讀寫等)

//mmap、munmap函數的使用 #include <stdio.h> #include <string.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <sys/mman.h>void sys_err(char *str) {perror(str);exit(1); }…

1017. A除以B (20)

本題要求計算A/B&#xff0c;其中A是不超過1000位的正整數&#xff0c;B是1位正整數。你需要輸出商數Q和余數R&#xff0c;使得A B * Q R成立。 輸入格式&#xff1a; 輸入在1行中依次給出A和B&#xff0c;中間以1空格分隔。 輸出格式&#xff1a; 在1行中依次輸出Q和R&#…

mmap父子進程間通信

父子等有血緣關系的進程之間也可以通過mmap建立的映射區來完成數據通信。但相應的要在創建映射區的時候指定對應的標志位參數flags&#xff1a;MAP_PRIVATE&#xff1a;&#xff08;私有映射&#xff09;父子進程各自獨占映射區&#xff1b;MAP_SHARED&#xff1a;&#xff08;…

匿名映射

通過使用我們發現&#xff0c;使用映射區來完成文件讀寫操作十分方便&#xff0c;父子進程間通信也較容易。但缺陷是&#xff0c;每次創建映射區一定要依賴一個文件才能實現。通常為了建立映射區要open一個temp文件&#xff0c;創建好了再unlink、close掉&#xff0c;比較麻煩。…

mmap無血緣關系進程間通信

實質上mmap是內核借助文件幫我們創建了一個映射區&#xff0c;多個進程之間利用該映射區完成數據傳遞。由于內核空間多進程共享&#xff0c;因此無血緣關系的進程間也可以使用mmap來完成通信。只要設置相應的標志位參數flags即可。若想實現共享&#xff0c;當然應該使用MAP_SHA…

【C++ Primer | 13】課后習題答案

文章目錄13.1.4節目練習13.2節練習13.2.2練習13.1.4節目練習 練習13.14 #include <iostream> using namespace std;class numbered { private: static int seq; public:numbered() { mysn seq; }int mysn; };int numbered::seq 0;void f(numbered s) { cout <…

信號的概念與機制

信號的共性&#xff1a;1. 簡單&#xff08;開銷小&#xff0c;且在用或者不用的情況下&#xff0c;開銷是一樣的&#xff09;&#xff1b;2. 不能攜帶大量信息&#xff08;如程序執行過程中&#xff0c;出現段錯誤時&#xff0c; 就會發送一個相關的信號&#xff08;編號為11&…

信號的產生和狀態

信號的產生&#xff1a;1.按鍵產生&#xff0c;如&#xff1a;Ctrlc&#xff08;內核向進程發送信號&#xff0c;殺死該進程&#xff09;、Ctrlz、Ctrl\&#xff1b;2.系統調用產生&#xff0c;如&#xff1a;kill、raise、abort&#xff1b;3.軟件條件產生&#xff0c;如&…

【C++ Priemr | 15】虛函數常見問題

1. 在成員函數中調用虛函數&#xff1a; #include <iostream> using namespace std; class CBase { public:void func1(){func2();}virtual void func2() { cout << "CBase::func2()" << endl; } }; class CDerived : public CBase { public:virt…

965. 單值二叉樹

如果二叉樹每個節點都具有相同的值&#xff0c;那么該二叉樹就是單值二叉樹。 只有給定的樹是單值二叉樹時&#xff0c;才返回 true&#xff1b;否則返回 false。 示例 1&#xff1a; 輸入&#xff1a;[1,1,1,1,1,null,1] 輸出&#xff1a;true示例 2&#xff1a; 輸入&#…

信號四要素

與變量三要素&#xff08;類型、名字、值&#xff09;類似的&#xff0c;每個信號也有其必備4要素&#xff0c;分別是&#xff1a;1.編號&#xff1b;2.名稱&#xff08;即編號的宏定義&#xff09; &#xff1b;3.事件&#xff08;引起信號產生的事件&#xff0c;如段錯誤&…

958. 二叉樹的完全性檢驗

給定一個二叉樹&#xff0c;確定它是否是一個完全二叉樹。 百度百科中對完全二叉樹的定義如下&#xff1a; 若設二叉樹的深度為 h&#xff0c;除第 h 層外&#xff0c;其它各層 (1&#xff5e;h-1) 的結點數都達到最大個數&#xff0c;第 h 層所有的結點都連續集中在最左邊&a…

信號的產生

&#xff08;1&#xff09;終端按鍵產生信號&#xff08;與終端交互的進程&#xff09; Ctrl c → 2) SIGINT&#xff08;終止/中斷&#xff09; "INT" ----Interrupt Ctrl z → 20) SIGTSTP&#xff08;暫停/停止&#xff09; "T" ----Termin…