你真的熟練掌握二分查找了嗎贮喧?

二分查找由于思想簡單,在經(jīng)典算法中最容易被初學(xué)者忽視猪狈。
看懂了書上的一種寫法后箱沦,就以為自己會了,而實(shí)際上是一看就會雇庙,一寫就廢谓形。

不信的話灶伊,先來看一個(gè)問題:
找出升序數(shù)組中小于等于目標(biāo)值的最大值?(數(shù)組中可能不包含目標(biāo)值)

如果感覺很棘手寒跳,別著急聘萨,看完本文你能隨手?jǐn)]出兩種不同的解法。如果你寫出來了童太,也別著急米辐,繼續(xù)看下去,你會發(fā)現(xiàn)你寫的不一定完美书释。好啦翘贮,不管怎樣,看完絕對不虧爆惧,哈哈狸页。

一、開門見山

不繞彎彎扯再,針對問題:找出升序數(shù)組中小于等于目標(biāo)值的最大值芍耘?
直接給出兩種二分查找的典型寫法,
第一種:

int binarySearch(int[] a, int target){
    int low = 0, high = a.length - 1;
    while(low <= high){
        int mid = (low + high) >>> 1;
        if(a[mid] < target) low = mid + 1;
        else if(a[mid] > target) high = mid - 1;
        else return mid;
    }
    return high; // 必須返回 high
}

第二種:

int binarySearch(int[] a, int target){
    int low = 0, high = a.length;
    while(low < high){ 
        int mid = (low + high + 1) >>> 1;
        if(a[mid] > target) high = mid - 1;
        else low = mid;
    }
    return low; // 返回 high 也可以
}

這兩種寫法最明顯的區(qū)別是熄阻,在 while 循環(huán)里斋竞,第一種是三分支判斷,第二種是兩分支判斷秃殉。

二分查找版坝初,找不同游戲:

  1. high 的初始值,有 a.length 和 a.length-1 兩種
  2. while 循環(huán)條件复濒,有 low < high 和 low <= high 兩種
  3. 取中位數(shù),有 取左中位數(shù) 和 取右中位數(shù) 兩種
  4. 取中位數(shù)的寫法乒省,以取左中位數(shù)為例巧颈,有 low+(high-low)/2、low+((high-low)>>1)袖扛、(low+high)>>>1(推薦這種寫法)等砸泛。注意 low+high 可能溢出,最好不要寫成 (low+high)/2
  5. low 和 high 的更新蛆封,以 low 為例唇礁,有 low = mid 和 low = mid+1 兩種
  6. 判斷分支的個(gè)數(shù),有兩分支和三分支兩種惨篱。

可見二分查找的寫法是多么靈活盏筐!

不過對于上面的找不同,有幾點(diǎn)需要說明一下砸讳。
high 的初值設(shè)置和 while 循環(huán)條件有關(guān)琢融,當(dāng)循環(huán)條件為 low <= high 時(shí)界牡,必須寫 a.length-1,否則兩種寫法都可以漾抬。
另外宿亡,只有兩分支寫法需要取右中位數(shù)和邊界更新為中位數(shù)(e.g. low = mid),后面會說到纳令。
簡單解釋一下挽荠,為什么 (low+high)>>>1 不用擔(dān)心溢出,溢出之所以出錯平绩,是因?yàn)橛幸徊糠謹(jǐn)?shù)值進(jìn)到了符號位圈匆,我們只要把符號位當(dāng)成是數(shù)值位,結(jié)果就是正確的馒过,而無符號右移臭脓,剛好就不會處理符號的問題。

二腹忽、第一種寫法:三分支二分查找(推薦寫法)

這種寫法来累,除了返回值以外基本上都可以閉著眼睛寫

int low = 0, high = a.length - 1;
while(low <= high){
    int mid = (low + high) >>> 1;
    if(a[mid] < target) low = mid + 1;
    else if(a[mid] > target) high = mid - 1;
    else return mid;
}

由于 while 循環(huán)條件是 low <= high,所以退出循環(huán)時(shí)窘奏,low != high嘹锁,至于最終是返回 low 還是 high。記住下面這種圖就 OK 了着裹。


二分查找.png

這幅圖的含義是领猾,如果二分查找的目標(biāo)值 target 不在數(shù)組中,那么最后退出循環(huán)時(shí)骇扇,high 指向數(shù)組中剛好小于 target 的值摔竿,low 指向數(shù)組中剛好大于 target 的值。

三少孝、第二種寫法:兩分支二分查找

while 循環(huán)條件是 low < high继低,退出循環(huán)時(shí),一定有 low == high稍走,不用糾結(jié)返回 low 還是返回 high袁翁。
循環(huán)里只寫兩個(gè)分支,一個(gè)分支排除中位數(shù)婿脸,另一個(gè)分支不排除中位數(shù)粱胜,不再單獨(dú)對中位數(shù)做判斷。
根據(jù)“排除中位數(shù)的邏輯”狐树,會有以下兩種情況:

if(排除中位數(shù)的邏輯) low = mid + 1;
else high = mid;
if(排除中位數(shù)的邏輯) high = mid - 1;
else low = mid;

因?yàn)橛幸粋€(gè)邊界更新為中位數(shù)焙压,為了避免死循環(huán),所以有 取左中位數(shù) 和 取右中位數(shù) 的區(qū)別。
例如下面的代碼就有陷入死循環(huán)的風(fēng)險(xiǎn):

mid = (low + high) >>> 1; // 選擇左中位數(shù)
if(排除中位數(shù)的邏輯) high = mid - 1;
else low = mid;

當(dāng)候選區(qū)間只剩兩個(gè)元素時(shí)冗恨,此時(shí)求得的左中位數(shù)就是左邊界答憔,一旦進(jìn)入左邊界更新為中位數(shù)的邏輯分支,下一次循環(huán)掀抹,左邊界沒有變虐拓,求得的左中位數(shù)還是左邊界,左邊界還是不變傲武,就會陷入死循環(huán)蓉驹。
解決辦法是星澳,對于左邊界更新為中位數(shù)的情況感混,我們?nèi)∮抑形粩?shù)。對于右邊界更新為中位數(shù)的情況区转,我們?nèi)∽笾形粩?shù)疟位。

四瞻润、具體問題

4.1 找出升序數(shù)組中小于等于目標(biāo)值的最大值

前面已經(jīng)給出代碼,不再重復(fù)列出甜刻。

4.2 找出升序數(shù)組中大于等于目標(biāo)值的最小值

第一種寫法:

int binarySearch(int[] a, int target){
    int low = 0, high = a.length - 1;
    while(low <= high){
        int mid = (low + high) >>> 1;
        if(a[mid] < target) low = mid + 1;
        else if(a[mid] > target) high = mid - 1;
        else return mid;
    }
    return low;
}

第二種寫法(小于目標(biāo)值的肯定不要绍撞,作為排除中位數(shù)的邏輯):

int binarySearch(int[] a, int target){
    int low = 0, high = a.length;
    while(low < high){
        int mid = (low + high) >>> 1;
        if(a[mid] < target) low = mid + 1;
        else high = mid;
    }
    return low;
}
4.3 找出升序數(shù)組中小于目標(biāo)值的最大值

第一種寫法:

int binarySearch(int[] a, int target){
    int low = 0, high = a.length - 1;
    while(low <= high){
        int mid = (low + high) >>> 1;
        if(a[mid] < target) low = mid + 1;
        else if(a[mid] > target) high = mid - 1;
        else return mid - 1;
    }
    return high;
}

第二種寫法(大于等于目標(biāo)值的肯定不要,作為排除中位數(shù)的邏輯):

int binarySearch(int[] a, int target){
    int low = 0, high = a.length;
    while(low < high){
        int mid = (low + high + 1) >>> 1;
        if(a[mid] >= target) high = mid - 1;
        else low = mid;
    }
    return low;
}

4.4 找出升序數(shù)組中大于目標(biāo)值的最小值

第一種寫法:

int binarySearch(int[] a, int target){
    int low = 0, high = a.length - 1;
    while(low <= high){
        int mid = (low + high) >>> 1;
        if(a[mid] < target) low = mid + 1;
        else if(a[mid] > target) high = mid - 1;
        else return mid + 1;
    }
    return low;
}

第二種寫法(小于等于目標(biāo)值的肯定不要得院,作為排除中位數(shù)的邏輯):

int binarySearch(int[] a, int target){
    int low = 0, high = a.length;
    while(low < high){
        int mid = (low + high) >>> 1;
        if(a[mid] <= target) low = mid + 1;
        else high = mid;
    }
    return low;
}

五傻铣、最后

不是說第一種寫法 while 循環(huán)條件一定要是 low <= high,只是 low <= high 更好祥绞。不是說第二種寫法 while 循環(huán)條件一定要是 low < high非洲,只是寫 low < high 更好。
我還是推薦大家使用第一種寫法蜕径,代碼對稱两踏,寫法固定,好記兜喻,只需要記住那張圖就可以了梦染。當(dāng)然,第二種寫法也不是白看的虹统,當(dāng)你看到別人用第二種寫法時(shí)弓坞,能很快反應(yīng)過來隧甚,他寫的是什么车荔,寫的正不正確。

參考:leetcode 題解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戚扳,一起剝皮案震驚了整個(gè)濱河市忧便,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖珠增,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件超歌,死亡現(xiàn)場離奇詭異,居然都是意外死亡蒂教,警方通過查閱死者的電腦和手機(jī)巍举,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凝垛,“玉大人懊悯,你說我怎么就攤上這事∶纹ぃ” “怎么了炭分?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剑肯。 經(jīng)常有香客問我捧毛,道長,這世上最難降的妖魔是什么让网? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任呀忧,我火速辦了婚禮,結(jié)果婚禮上寂祥,老公的妹妹穿的比我還像新娘荐虐。我一直安慰自己,他們只是感情好丸凭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布福扬。 她就那樣靜靜地躺著,像睡著了一般惜犀。 火紅的嫁衣襯著肌膚如雪铛碑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天虽界,我揣著相機(jī)與錄音汽烦,去河邊找鬼。 笑死莉御,一個(gè)胖子當(dāng)著我的面吹牛撇吞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播礁叔,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼牍颈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了琅关?” 一聲冷哼從身側(cè)響起煮岁,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后画机,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冶伞,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年步氏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了响禽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荚醒,死狀恐怖金抡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腌且,我是刑警寧澤梗肝,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站铺董,受9級特大地震影響巫击,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜精续,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一坝锰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧重付,春花似錦顷级、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至删掀,卻和暖如春翔冀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背披泪。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工纤子, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人款票。 一個(gè)月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓控硼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艾少。 傳聞我的和親對象是個(gè)殘疾皇子卡乾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容