二分查找

店小二:掌柜的,您進(jìn)貨回來了呀鳍寂,喲改含!今天您買這魚挺大呀!袁廚:那是迄汛,這是我今天從咱們江邊買的捍壤,之前一直去菜市場(chǎng)買刃唤,那里的老貴了,你猜猜我今天買的多少錢一條白群。店小二:之前的魚,30 個(gè)銅板一條硬霍,今天的我猜 26 個(gè)銅板帜慢。袁廚:貴了。店小二:還貴呀唯卖!那我猜 20 個(gè)銅板粱玲!袁廚:還是貴了。店小二:15 個(gè)銅板拜轨。袁廚:便宜了店小二:18 個(gè)銅板袁廚:恭喜你猜對(duì)了

上面的例子就用到了我們的二分查找思想抽减,如果你玩過類似的游戲,那二分查找理解起來肯定很輕松啦橄碾。

二分查找

二分查找也稱折半查找(Binary Search)卵沉,是一種在有序數(shù)組中查找某一特定元素的搜索算法。我們可以從定義可知法牲,運(yùn)用二分搜索的前提是數(shù)組必須是有序的史汗,這里需要注意的是,我們的輸入不一定是數(shù)組拒垃,也可以是數(shù)組中某一區(qū)間的起始位置和終止位置停撞。通過上面二分查找的定義,我們知道了二分查找算法的作用及要求悼瓮,那么該算法的具體執(zhí)行過程是怎樣的呢戈毒?下面我們通過一個(gè)例子來幫助我們理解。我們需要在 nums 數(shù)組中横堡,查詢?cè)?8 的索引埋市。

圖片

1. 我們需要定義兩個(gè)指針分別指向數(shù)組的頭部及尾部,這是我們?cè)谡麄€(gè)數(shù)組中查詢的情況翅萤,當(dāng)我們?cè)跀?shù)組某一區(qū)間進(jìn)行查詢時(shí)恐疲,可以輸入數(shù)組,起始位置套么,終止位置進(jìn)行查詢培己。
圖片
  1. 找出 mid,該索引為 mid =(left + right)/ 2胚泌,但是這樣寫有可能溢出省咨,所以我們需要改進(jìn)一下寫成 mid = left +(right - left)/ 2 或者 left + ((right - left ) >> 1) 兩者作用是一樣的,都是為了找到兩指針的中間索引玷室,使用位運(yùn)算的速度更快零蓉。那么此時(shí)的 mid = 0 + (8-0) / 2 = 4
    圖片

3. 此時(shí)我們的 mid = 4笤受,nums[mid] = 6 < target,那么我們需要移動(dòng)我們的 left 指針敌蜂,讓left = mid + 1箩兽,下次則可以在新的 left 和 right 區(qū)間內(nèi)搜索目標(biāo)值,下圖為移動(dòng)前和移動(dòng)后
圖片

4. 我們需要在 left 和 right 之間計(jì)算 mid 值章喉,mid = 5 + (8 - 5)/ 2 = 6 然后將 nums[mid] 與 target 繼續(xù)比較汗贫,進(jìn)而決定下次移動(dòng)left 指針還是 right 指針,見下圖
圖片

5. 我們發(fā)現(xiàn) nums[mid] > target秸脱,則需要移動(dòng)我們的 right 指針落包, 則 right = mid - 1;則移動(dòng)過后我們的 left 和 right 會(huì)重合摊唇,這里是我們的一個(gè)重點(diǎn)大家需要注意一下咐蝇,后面會(huì)對(duì)此做詳細(xì)敘述。
圖片

6. 我們需要在 left 和 right 之間繼續(xù)計(jì)算 mid 值巷查,則 mid = 5 +(5 - 5)/ 2 = 5 有序,見下圖,此時(shí)我們將 nums[mid] 和 target 比較岛请,則發(fā)現(xiàn)兩值相等笔呀,返回 mid 即可 ,如果不相等則跳出循環(huán)髓需,返回 -1许师。
圖片
圖片

二分查找的執(zhí)行過程如下
****二分查找的執(zhí)行過程如下****

  • 從已經(jīng)排好序的數(shù)組或區(qū)間中,取出中間位置的元素僚匆,將其與我們的目標(biāo)值進(jìn)行比較微渠,判斷是否相等,如果相等則返回咧擂。

  • 如果 nums[mid] 和 target 不相等逞盆,則對(duì) nums[mid] 和 target 值進(jìn)行比較大小,通過比較結(jié)果決定是從 mid的左半部分還是右半部分繼續(xù)搜索松申。

如果 target > nums[mid] 則右半?yún)^(qū)間繼續(xù)進(jìn)行搜索云芦,即 left = mid + 1; 若target < nums[mid] 則在左半?yún)^(qū)間繼續(xù)進(jìn)行搜索,即 right = mid -1贸桶;動(dòng)圖解析

圖片

下面我們來看一下二分查找的代碼舅逸,可以認(rèn)真思考一下 if 語句的條件,每個(gè)都沒有簡(jiǎn)寫皇筛。
圖片

二分查找的思路及代碼已經(jīng)理解了琉历,那么我們來看一下實(shí)現(xiàn)時(shí)容易出錯(cuò)的地方。 易錯(cuò)點(diǎn):

  1. 計(jì)算 mid 時(shí) ,不能使用 (left + right )/ 2旗笔,否則有可能會(huì)導(dǎo)致溢出

  2. while (left < = right)彪置,注意括號(hào)內(nèi)為 left <= right ,而不是 left < right 蝇恶,我們繼續(xù)回顧剛才的例子拳魁,如果我們?cè)O(shè)置條件為 left < right 則當(dāng)我們執(zhí)行到最后一步時(shí),則我們的 left 和 right 重疊時(shí)撮弧,則會(huì)跳出循環(huán)的猛,返回 -1,區(qū)間內(nèi)不存在該元素想虎,但是不是這樣的,我們的 left 和 right 此時(shí)指向的就是我們的目標(biāo)元素 叛拷,但是此時(shí) left = right 跳出循環(huán)

  3. left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid舌厨。我們思考一下這種情況,見下圖,當(dāng)我們的target 元素為 16 時(shí)忿薇,然后我們此時(shí) left = 7 裙椭,right = 8,mid = left + (right - left) = 7 + (8-7) = 7署浩,那如果設(shè)置 left = mid 的話揉燃,則會(huì)進(jìn)入死循環(huán),mid 值一直為7 筋栋。

圖片

下面我們來看一下二分查找的遞歸寫法

圖片

例題及解析

例題:

題目來源:leetcode35 搜索插入位置 給定一個(gè)排序數(shù)組和一個(gè)目標(biāo)值炊汤,在數(shù)組中找到目標(biāo)值,并返回其索引弊攘。如果目標(biāo)值不存在于數(shù)組中抢腐,返回它將會(huì)被按順序插入的位置。你可以假設(shè)數(shù)組中無重復(fù)元素襟交。示例 1:

輸入: [1,3,5,6], 5 輸出: 2

示例 2:

輸入: [1,3,5,6], 2 輸出: 1

示例 3:

輸入: [1,3,5,6], 7 輸出: 4

示例 4:

輸入: [1,3,5,6], 0 輸出: 0

題目解析

這個(gè)題目完全就和咱們的二分查找一樣迈倍,只不過有了一點(diǎn)改寫,那就是將咱們的返回值改成了 left捣域,具體實(shí)現(xiàn)過程見下圖
圖片
圖片

二分查找變種一

上面我們說了如何使用二分查找在數(shù)組或區(qū)間里查出特定值的索引位置啼染。但是我們剛才數(shù)組里面都沒有重復(fù)值,查到返回即可焕梅,那么我們思考一下下面這種情況:
圖片

此時(shí)我們數(shù)組里含有多個(gè) 5 迹鹅,我們查詢是否含有 5 可以很容易查到,但是我們想獲取第一個(gè) 5 和 最后一個(gè) 5 的位置應(yīng)該怎么實(shí)現(xiàn)呢贞言?哦徒欣!我們可以使用遍歷,當(dāng)查詢到第一個(gè) 5 時(shí)蜗字,我們?cè)O(shè)立一個(gè)指針進(jìn)行定位打肝,然后到達(dá)最后一個(gè) 5 時(shí)返回脂新,這樣我們就能求的第一個(gè)和最后一個(gè)五了?因?yàn)槲覀冞@個(gè)文章的主題就是二分查找粗梭,我們可不可以用二分查找來實(shí)現(xiàn)呢争便?當(dāng)然是可以的。

題目描述

題目來源:leetcode 34 在排序數(shù)組中查找元素的第一個(gè)和最后一個(gè)位置給定一個(gè)按照升序排列的整數(shù)數(shù)組 nums断医,和一個(gè)目標(biāo)值 target滞乙。找出給定目標(biāo)值在數(shù)組中的開始位置和結(jié)束位置。如果數(shù)組中不存在目標(biāo)值 target鉴嗤,返回 [-1, -1]斩启。示例 1:

輸入:nums = [5,7,7,8,8,10], target = 8 輸出:[3,4]

示例 2:

輸入:nums = [5,7,7,8,8,10], target = 6 輸出:[-1,-1]

示例 3:

輸入:nums = [], target = 0 輸出:[-1,-1]

題目解析

這個(gè)題目很容易理解,我們?cè)谏厦嬲f了如何使用遍歷解決該題醉锅,但是這個(gè)題目的目的就是讓我們使用二分查找兔簇,我們來逐個(gè)分析,先找出目標(biāo)元素的下邊界硬耍,那么我們?nèi)绾握业侥繕?biāo)元素的下邊界呢垄琐?我們來重點(diǎn)分析一下剛才二分查找中的這段代碼
圖片

我們只需在這段代碼中修改即可,我們?cè)賮砥饰鲆幌逻@塊代碼经柴,nums[mid] == target 時(shí)則返回狸窘,nums[mid] < target 時(shí)則移動(dòng)左指針,在右區(qū)間進(jìn)行查找坯认, nums[mid] > target 時(shí)則移動(dòng)右指針翻擒,在左區(qū)間內(nèi)進(jìn)行查找。那么我們思考一下牛哺,如果此時(shí)我們的 nums[mid] = target 韭寸,但是我們不能確定 mid 是否為該目標(biāo)數(shù)的左邊界,所以此時(shí)我們不可以返回下標(biāo)荆隘。例如下面這種情況恩伺。
圖片

此時(shí) mid = 4 ,nums[mid] = 5椰拒,但是此時(shí)的 mid 指向的并不是第一個(gè) 5晶渠,所以我們需要繼續(xù)查找 ,因?yàn)槲覀円业氖菙?shù)的下邊界燃观,所以我們需要在 mid 的值的左區(qū)間繼續(xù)尋找 5 褒脯,那我們應(yīng)該怎么做呢?我們只需在target <= nums[mid] 時(shí)缆毁,讓 right = mid - 1即可番川,這樣我們就可以繼續(xù)在 mid 的左區(qū)間繼續(xù)找 5 。是不是聽著有點(diǎn)繞,我們通過下面這組圖進(jìn)行描述颁督。
圖片
圖片

其實(shí)原理很簡(jiǎn)單践啄,就是我們將小于和等于合并在一起處理,當(dāng) target <= nums[mid] 時(shí)沉御,我們都移動(dòng)右指針屿讽,也就是 right = mid -1,還有一個(gè)需要注意的就是吠裆,我們計(jì)算下邊界時(shí)最后的返回值為 left 伐谈,當(dāng)上圖結(jié)束循環(huán)時(shí),left = 3试疙,right = 2诵棵,返回 left 剛好時(shí)我們的下邊界。我們來看一下求下邊界的具體執(zhí)行過程祝旷。動(dòng)圖解析

圖片

計(jì)算下邊界代碼
圖片

計(jì)算上邊界時(shí)算是和計(jì)算上邊界時(shí)條件相反履澳,計(jì)算下邊界時(shí),當(dāng) target <= nums[mid] 時(shí)缓屠,right = mid -1;target > nums[mid] 時(shí)护侮,left = mid + 1敌完;計(jì)算上邊界時(shí),當(dāng) target < nums[mid] 時(shí)羊初,right = mid -1; target >= nums[mid] 時(shí) left = mid + 1;剛好和計(jì)算下邊界時(shí)條件相反滨溉,返回right。計(jì)算上邊界代碼
圖片

題目完整代碼
圖片

二分查找變種二

我們?cè)谏厦娴淖兎N中长赞,描述了如何找出目標(biāo)元素在數(shù)組中的上下邊界晦攒,然后我們下面來看一個(gè)新的變種,如何從數(shù)組或區(qū)間中找出第一個(gè)大于或最后一個(gè)小于目標(biāo)元素的數(shù)的索引得哆,例 nums = {1,3,5,5,6,6,8,9,11} 我們希望找出第一個(gè)大于 5的元素的索引脯颜,那我們需要返回 4 ,因?yàn)?5 的后面為 6贩据,第一個(gè) 6 的索引為 4栋操,如果希望找出最后一個(gè)小于 6 的元素,那我們則會(huì)返回 3 饱亮,因?yàn)?6 的前面為 5 最后一個(gè) 5 的索引為 3矾芙。題目我們已經(jīng)了解,下面我們先來看一下如何在數(shù)組或區(qū)間中找出第一個(gè)大于目標(biāo)元素的數(shù)吧近上。找出第一個(gè)大于目標(biāo)元素的數(shù)剔宪,大概有以下幾種情況
圖片
  1. 數(shù)組包含目標(biāo)元素娩嚼,找出在他后面的第一個(gè)元素;

  2. 目標(biāo)元素不在數(shù)組中饲鄙,且數(shù)組中的所有元素都大于它门扇,那么我們此時(shí)返回?cái)?shù)組的第一個(gè)元素即可;

  3. 目標(biāo)元素不在數(shù)組中哈街,數(shù)組內(nèi)的部分元素大于它留瞳,此時(shí)我們需要返回第一個(gè)大于他的元素;

  4. 目標(biāo)元素不在數(shù)組中骚秦,且數(shù)組中的所有元素都小于它她倘,那么我們此時(shí)沒有查詢到,返回 -1 即可作箍。

既然我們已經(jīng)分析完所有情況硬梁,那么這個(gè)題目對(duì)咱們就沒有難度了,下面我們描述一下案例的執(zhí)行過程

nums = {1,3,5,5,6,6,8,9,11} target = 7

上面的例子中胞得,我們需要找出第一個(gè)大于 7 的數(shù)荧止,那么我們的程序是如何執(zhí)行的呢?上面的例子我們已經(jīng)弄懂了阶剑,那么我們看一下跃巡,當(dāng) target = 0時(shí),程序應(yīng)該怎么執(zhí)行呢牧愁?
圖片

OK素邪!我們到這一步就能把這個(gè)變種給整的明明白白的了,下面我們看一哈程序代碼吧猪半,也是非常簡(jiǎn)單的兔朦。
圖片

通過上面的例子我們應(yīng)該可以完全理解了那個(gè)變種。下面我們繼續(xù)來看以下這種情況磨确,那就是如何找到最后一個(gè)小于目標(biāo)數(shù)的元素沽甥。還是上面那個(gè)例子

nums = {1,3,5,5,6,6,8,9,11} target = 7

查找最后一個(gè)小于目標(biāo)數(shù)的元素,比如我們的目標(biāo)數(shù)為 7 乏奥,此時(shí)他前面的數(shù)為 6摆舟,最后一個(gè) 6 的索引為 5,此時(shí)我們返回 5 即可邓了,如果目標(biāo)數(shù)元素為 12盏檐,那么我們最后一個(gè)元素為 11,仍小于目標(biāo)數(shù)驶悟,那么我們此時(shí)返回 8胡野,即可。這個(gè)變種其實(shí)算是上面變種的相反情況痕鳍,上面的會(huì)了硫豆,這個(gè)也完全可以搞定了龙巨,下面我們看一下代碼吧。
圖片
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末熊响,一起剝皮案震驚了整個(gè)濱河市旨别,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汗茄,老刑警劉巖秸弛,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異洪碳,居然都是意外死亡递览,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門瞳腌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绞铃,“玉大人,你說我怎么就攤上這事嫂侍《酰” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵挑宠,是天一觀的道長(zhǎng)菲盾。 經(jīng)常有香客問我,道長(zhǎng)各淀,這世上最難降的妖魔是什么懒鉴? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮揪阿,結(jié)果婚禮上疗我,老公的妹妹穿的比我還像新娘咆畏。我一直安慰自己南捂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布旧找。 她就那樣靜靜地躺著溺健,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钮蛛。 梳的紋絲不亂的頭發(fā)上鞭缭,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音魏颓,去河邊找鬼岭辣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甸饱,可吹牛的內(nèi)容都是我干的沦童。 我是一名探鬼主播仑濒,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼偷遗!你這毒婦竟也來了墩瞳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤氏豌,失蹤者是張志新(化名)和其女友劉穎喉酌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泵喘,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泪电,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涣旨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歪架。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖霹陡,靈堂內(nèi)的尸體忽然破棺而出和蚪,到底是詐尸還是另有隱情,我是刑警寧澤烹棉,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布攒霹,位于F島的核電站,受9級(jí)特大地震影響浆洗,放射性物質(zhì)發(fā)生泄漏催束。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一伏社、第九天 我趴在偏房一處隱蔽的房頂上張望抠刺。 院中可真熱鬧,春花似錦摘昌、人聲如沸速妖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罕容。三九已至,卻和暖如春稿饰,著一層夾襖步出監(jiān)牢的瞬間锦秒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工喉镰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旅择,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓侣姆,卻偏偏與公主長(zhǎng)得像生真,于是被迫代替她去往敵國(guó)和親脖咐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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