算法學習(六): 二分搜索

定義


從一個簡單問題說起

給定一個排序并不存在重復元素的數(shù)組: [1,2,5,7,8,9,13], 查找8的位置

暴力解法: 遍歷整個數(shù)組, 找到與給定值相同的元素, 返回下標, 時間復雜度O(n)

另一種解法:

  1. 我們可以先取數(shù)組中間位置的值, 看中間位置的值和目標值的大小, 假如中間位置的值大于目標值, 則說明目標值處于數(shù)組的中間位置的左半部分, 假如中間位置的值小于目標值, 則說明目標值處于數(shù)組中間位置的右半部分
  2. 這里我們假設(shè)數(shù)組中間位置的值大于目標值, 則說明目標值處于數(shù)組中間值的左半部分, 那么右半部分我們就不需要再去查找了
  3. 對于中間值的左半部分我們繼續(xù)取中間位置的值, 像上面那樣我們繼續(xù)判斷中間位置的值的大小, 大于目標值我們?nèi)∽蟀氩糠? 小于目標值我們?nèi)∮野氩糠?/li>
  4. 按照上面每次去一半的方法一直分下去, 直到數(shù)組中的某個位置值與目標值相等, 返回該位置

時間復雜度: 由于我們每次分割都會少掉一半, 所以時間復雜度為O(logn)


二分搜索

上面這種每次取一半搜索的方法就是二分搜索

二分搜索注意事項

  • 對輸入做異常處理: 數(shù)組為空或者數(shù)組長度為0
  • mid = start + (end - start) / 2 這種表示方法可以防止兩個整型值相加時溢出
  • 使用迭代而不是遞歸進行二分查找, 因為工程中遞歸寫法存在潛在溢出的可能
  • while終至條件: start + 1 < end而不是start <= end, start == end時可能出現(xiàn)死循環(huán)
  • 迭代終至時target應為start或者end中的一個募壕。循環(huán)終至條件有兩個, 具體應看是找到第一個還是最后一個而定
function binarySearch(arr, target) {
  if (arr.length < 1) {
    return -1
  }
  let start = 0
  let end = arr.length - 1
  while (start + 1 < end) {
    let mid = Math.floor(start + (end - start) / 2)
    if (arr[mid] >= target) {
      end = mid
    }else if (arr[mid] < target) {
      start = mid
    }
  }
  
  if (arr[start] === target) {
    return start
  }
  if (arr[end] === target) {
    return end
  }
return -1
}


例題


搜索插入位置

給定一個排序數(shù)組和一個目標值, 如果在數(shù)組中找到了目標值則返回索引。
如果沒有, 返回到它將會被按順序插入的位置差油。
你可以假設(shè)在數(shù)組中無重復元素

case1:
輸入: [1,3,5,6], 5 輸出: 2
case2:
輸入: [1,3,5,6], 2 輸出: 1
case3:
輸入: [1,3,5,6], 7 輸出: 4
case4:
輸入: [1,3,5,6], 0 輸出: 0

分析:

  • 在目標值是數(shù)組中的數(shù)時, 很明顯這就是標準的二分搜索的題
  • 在目標值不是數(shù)組中的數(shù)時, 情況分三種情況:
    1. 目標值在數(shù)組中某兩個數(shù)大小之間, 上面題的case2就是這種情況, 這種情況下, 目標值的大小一定是處在數(shù)組某相鄰兩個數(shù)之間, 換句話說也就是目標值永遠插入在start+1的位置
    2. 目標值比數(shù)組起始位置數(shù)還小, case4, 這種情況永遠返回0
    3. 目標值比數(shù)組最后一位的值還大. case3, 此時永遠返回arr.length + 1
function searchInsert(arr, target) {
  if (arr.length < 1) {
    return 0
  }
  
  let start = 0
  let end = arr.length - 1
  
  while (start + 1 < end) {
    let mid = Math.floor(start + (end - start) / 2)
    if (arr[mid] < target) {
      start = mid
    } else {
      end = mid
    }
  }
  if (arr[start] === target) {
    return start
  }
  if (arr[end] === target) {
    return end
  }
  if (target > arr[end]) {
    return end + 1
  }
  if (target < arr[start]) {
    return 0
  }
  return start + 1
}


搜索二維矩陣

編寫一個高效的算法來搜索m*n矩陣中的一個目標值。
該矩陣具有以下特性:
每行中的整數(shù)從左向右排序米死。
每行的第一個數(shù)大于前一行的最后一個整數(shù)莱找。

例如:
以下矩陣:
1???2???3???4
5???6???7???8
11 13 15 17
56 78 89 98
給定一個目標值3, 返回下標

分析:

  • 由于每一行第一個數(shù)都比前一行最后一個數(shù)大, 所有這個矩陣轉(zhuǎn)換成一維數(shù)組的話, 是一個排好序的一維數(shù)組, 這個問題就可以轉(zhuǎn)換成一維數(shù)組搜索, 也就是最基本的二維數(shù)組問題
  • 矩陣每一行4個元素, 第二行第一個matrix[1][0]轉(zhuǎn)換成一維數(shù)組時下標為1 * 4 + 0 = 4
    矩陣第三行第二個matrix[2][1]轉(zhuǎn)換成一維數(shù)組時下標為 2 * 4 + 1 = 9
    以此類推, matrix[n][m]轉(zhuǎn)換成一維數(shù)組時下標為n * 4 + m
    一維數(shù)組arr[n]轉(zhuǎn)換成每組有m個數(shù)的martix時下標為matrix[n/m][n%m]
function binarySearch(matrix, target) {
  if (matrix.length < 1 || matrix[0].length < 1) {
    return -1
  }
  
  let start = 0
  let end = matrix.length * matrix[0].length - 1

  while (start + 1 < end) {
    let mid = Math.floor(start + (end - start) / 2)
    if (matrix[Math.floor(mid / matrix[0].length)][mid % matrix[0].length] < target) {
      start = mid
    } else {
      end = mid
    }
  }
  if (matrix[Math.floor(start / matrix[0].length)][start % matrix[0].length] === target) {
    return [Math.floor(start / matrix[0].length), start % matrix[0].length]
  }
  if (matrix[Math.floor(end / matrix[0].length)][end % matrix[0].length] === target) {
    return [Math.floor(end / matrix[0].length), end % matrix[0].length]
  }
  return -1
}


求整數(shù)的平方根

給定一個整數(shù), 返回它的平方根, 平方根不是整數(shù)的, 向下取整
例如:
輸入: 25 輸出: 5
輸入: 0 輸出: 0
輸入: 9 輸出: 3
輸入: 7 輸出: 2

分析:
這道題可以用二分搜索來做

  • 取0到n中間的數(shù)m
  • m的平方大于n的話說明, n的平方根在0到m之間
  • 去0到m中間的值, 繼續(xù)平方, 如果大于n, 說明n的平方根在0到m/2之間, 如果小于m, 說明在m/2到m之間
  • 依次類推, 直到找到結(jié)果, 如果分到?jīng)]辦法再分下去還沒有找到結(jié)果, 則返回最后一次取值范圍的左邊界
function mySqrt(n) {
 if (n === 0) {
   return 0
 }else if (n < 0) {
   return 'error'
 }
 
 let start = 0
 let end = n
 while (start + 1 < end) {
   let mid = Math.floor(start + (end - start) / 2)
   if (mid * mid < n) {
     start = mid
   } else {
     end = mid
   }
 }
 if (end === n / end) {
   return end
 }
 return start
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堰怨,一起剝皮案震驚了整個濱河市兔簇,隨后出現(xiàn)的幾起案子啊鸭,更是在濱河造成了極大的恐慌锹淌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赠制,死亡現(xiàn)場離奇詭異赂摆,居然都是意外死亡,警方通過查閱死者的電腦和手機钟些,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門烟号,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人政恍,你說我怎么就攤上這事汪拥。” “怎么了篙耗?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵迫筑,是天一觀的道長宪赶。 經(jīng)常有香客問我,道長铣焊,這世上最難降的妖魔是什么逊朽? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任罕伯,我火速辦了婚禮曲伊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘追他。我一直安慰自己坟募,他們只是感情好,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布邑狸。 她就那樣靜靜地躺著懈糯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪单雾。 梳的紋絲不亂的頭發(fā)上赚哗,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音硅堆,去河邊找鬼屿储。 笑死,一個胖子當著我的面吹牛渐逃,可吹牛的內(nèi)容都是我干的够掠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼茄菊,長吁一口氣:“原來是場噩夢啊……” “哼疯潭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起面殖,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤竖哩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后脊僚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體相叁,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年吃挑,在試婚紗的時候發(fā)現(xiàn)自己被綠了钝荡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡舶衬,死狀恐怖埠通,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逛犹,我是刑警寧澤端辱,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布梁剔,位于F島的核電站,受9級特大地震影響舞蔽,放射性物質(zhì)發(fā)生泄漏荣病。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一渗柿、第九天 我趴在偏房一處隱蔽的房頂上張望个盆。 院中可真熱鬧,春花似錦朵栖、人聲如沸颊亮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽终惑。三九已至,卻和暖如春门扇,著一層夾襖步出監(jiān)牢的瞬間雹有,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工臼寄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留霸奕,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓脯厨,卻偏偏與公主長得像铅祸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子合武,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

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

  • 文/洛小簡 【單身狗】 我是一個狗临梗,但并非一條或一只。吃了葡萄的人羨慕我的日子稼跳,我卻垂涎著葡萄盟庞。世界真是太奇妙,追...
    洛小簡閱讀 493評論 8 3
  • 今天又刷了一遍《時空戀旅人》汤善,不一樣的感觸什猖,一樣淚目。 霧霾藍畫質(zhì)红淡,溫潤空曠寧靜美好不狮。情節(jié)簡單卻不單調(diào),沒有跌宕起...
    十禾石閱讀 688評論 0 4
  • 離人下酒何須愁在旱,醉手抱缸駕晚舟摇零。 一笑風輕吹舊夜,高歌滄海不回頭桶蝎。
    玩笑的熊閱讀 267評論 64 22
  • 現(xiàn)在到了11點驻仅,媽媽谅畅,還有一個小時就回來了,我想了一會兒噪服,決定幫媽媽做飯毡泻,可是 做什么呢?我在廚房里粘优,左看右看仇味,發(fā)...
    李君毅閱讀 161評論 0 0
  • (一) 江湖是啥? 江湖是一個夢敬飒。 江湖是放不下的俠客夢产艾。 我溫華溫大俠提著木劍沖入江湖罢屈,路上遇到一伙人:一個爺們...
    muer724閱讀 408評論 4 0