每週一算法之二分查找(Kotlin描述)

簡述: 從這篇文章起就會開啓另外一個系列就是上篇文章中提到的每週學習一個基本算法,會結合LeetCode上題目來作分析。解題的語言通常是Kotlin或Java. 若是涉及到一些有關Kotlin的知識點也會作一些介紹。若是平時就養成學習數據結構算法以及刷題的習慣,無論從此你是面試(願今後不再是面試造火箭平時擰螺絲了)或在實際上工做中都會對你有很大幫助。這也是這個系列文章的目的。面試

1、時間複雜度

最壞時間複雜度 O(log n)算法

最優時間複雜度 O(1)數組

平均時間複雜度 O(log n)bash

2、基本思想

在一個有序的列表中,要查找的數與列表中間的位置相比,若相等說明找到了,若要查找的數大於列表中間的數,說明要查找的數可能在有序列表的後半部分;若要查找的數小於列表中間的數,說明要查找的數可能在有序列表的前半部分;而後相似上述操做縮短查找範圍,最後直到查找到最後一個數,若是不等於要查找的數,那麼查找範圍就爲空。數據結構

3、算法步驟

給定一個包含n個帶值的元素數組或序列A[0], ... A[n-1],使A[0] <= ... <= A[n-1],以及目標值Target.app

  • 一、令 low = 0, high = n - 1
  • 二、若low > high, 則表示查找失敗結束
  • 三、令mid(中間值元素)爲 (low + high) / 2的值向下取整 (注意: 在具體實現中爲了防止溢出,通常會採用 low + (high - low) / 2的值向下取整 或者直接採用位運算的移位運算來實現除2的操做。這個後面會有具體題目來講明)
  • 四、若Target > A[mid], 令 low = mid + 1 (說明要查找的值在序列或數組後半部分(除去自身),移動low遊標,縮小查找範圍),並回到步驟2
  • 五、若是Target < A[mid], 令 high = mid - 1 (說明要查找的值在序列或數組前半部分(除去自身),移動high遊標,縮小查找範圍),並回到步驟2
  • 六、若是Target == A[mid], 查找成功並結束,返回mid下標值。

4、算法過程演示

5、代碼實現(Kotlin語言描述)

二分查找算法主要有兩種實現方式,一種是循環遞推的方式,另外一種則是遞歸的方式函數

  • 一、 循環遞推實現方式
  • 二、遞歸實現方式

6、爲何Java中mid = (low + high) / 2方式會溢出

相信刷過LeetCode題目的小夥伴們可能會在刷二分查找的題目過程會遇到超過期間限制的錯誤 post

不知道你們有沒有去分析過爲何會獲得超時啊,我都用了二分查找了時間複雜度都變成 O(log n) 呢,爲啥還會超時呢。其實是int數據類型溢出致使出現負數,使得代碼進入了死循環,而後致使超時,最後OJ給你個超出時間錯誤。

  • 一、出現問題的緣由

咱們能夠肯定 lowhigh 都是非負數,那麼也就是二進制表示的最高位符號位是0,而且lowhigh 都是31位二進制的整數性能

假設下面這種場景:學習

high = 0100 0000 0000 0000 0000 0000 0000 0000 = 1073741824 (Integer.MAX_VALUE的一半)
low  = 0100 0000 0000 0000 0000 0000 0000 0000 = 1073741824 (Integer.MAX_VALUE的一半)
複製代碼

當執行 low + high 操做時,進行二進制運算,以下

high + low = 1000 0000 0000 0000 0000 0000 0000 0000
複製代碼

針對上述high + low 運算的結果,若是是無符號的32位(4個字節)Integer來講就表示 2147483648 (Integer.MAX_VALUE的大小);若是是有符號的32位(4個字節)Integer來講就表示 -2147483648。 須要特別注意的是Java或Kotlin中是不支持無符號的Integer類型,只存在有符號的Integer類型

因此問題就來了,若是是在Java或Kotlin中 (low + high) / 2的值就變成了負數 -1073741824low = mid + 1, low就變成負數了。而後target的值會一直比mid要大 low就不斷累加,直到low又累加到1073741824mid 又變成 -1073741824,不斷往復進入了死循環致使超時。能夠看下面這個例子:

運算結果:

  • 二、解決該問題的方式

針對上述問題,你可能看到兩種解決問題的辦法,一種是採用 low + (high - low) / 2,另外一種就是 (low + high) >>> 1 或 Kotlin中的 (low + high) ushr 1.

(high + low) >>> 1 = 0100 0000 0000 0000 0000 0000 0000 0000 = 1073741824
複製代碼

一塊兒來看下例子:

運行結果:

7、補充一下Kotlin和Java中的位運算的知識點

  • 一、Java中的 >>>>> (或Kotlin中的 ushrshr ) 的區別

實際上無符號右移運算符>>>(或kotlin中的ushr)和右移運算符>>(或kotlin中的shr)是同樣的,只不過右移時左邊是補上符號位,而無符號右移運算符是補上0

  • 二、Kotlin中的位運算

在Kotlin中拋棄了Java那種直接使用 >>>、>>、<<、&、~、|、^這些非語義化的符號來實現位運算,說真的這樣符號對代碼可讀性確實下降了不少,看過源碼小夥伴就知道,不少源碼中爲了追求代碼的運行效率,每每會採用位運算,可是代碼理解和讀起來就有點費力了。然而很高興的是Kotlin卻採用一種更加語義化的中綴調用函數(infix)來實現位運算,可以作到真正的簡明識義, 而且用起來就像是在使用運算符同樣,可是它更加具備含義。

8、LeetCode上二分查找相關的題目(練一練)

注意: 在作二分查找題目以前,給幾點建議。

  • 一、真正在作題過程不多會有直接寫標準的二分查找的題目,通常都是須要變型,轉化成二分查找的問題。因此掌握二分查找思想比掌握實現方式更重要。
  • 二、通常是二分查找去解題有個很明顯的特徵那就是 升序數組或有序數組,以及在一些查找數中對時間複雜度要求比較高,好比時間複雜度必須低於O(n), 很明顯你不能直接用循環去作,二分查找的平均時間複雜度是O(log n) 明顯低於 O(n), 可能就須要你考慮是否能用二分查找。
  • 三、還有一個典型使用二分查找的題目,就是求平方根或者求徹底平方數,有個通用結論是: 一個非負數n的平方根小於n/2 + 1。因此就轉化了從[0,n/2 + 1]查找符合的平方根或徹底平方數。
  • 一、兩數之和 II - 輸入有序數組

  • 二、有效的徹底平方數

  • 三、x的平方根

  • 四、山脈數組的峯頂索引

  • 五、標準的二分查找

  • 六、尋找比目標字母大的最小字母

  • 七、猜數字大小

  • 八、第一個錯誤的版本

  • 九、求兩個數組的交集

  • 十、兩個數組的交集 II

歡迎關注Kotlin開發者聯盟,這裏有最新Kotlin技術文章,每週會不按期翻譯一篇Kotlin國外技術文章。若是你也喜歡Kotlin,歡迎加入咱們~~~

Kotlin系列文章,歡迎查看:

原創系列:

Effective Kotlin翻譯系列

翻譯系列:

實戰系列:

相關文章
相關標籤/搜索