LeetCode總結--二分查詢篇
阿新 • • 發佈:2018-11-16
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
二分查詢演算法雖然簡單,但面試中也比較常見,經常用來在有序的數列查詢某個特定的位置。在LeetCode用到此演算法的主要題目有:Search Insert Position
Sqrt(x)
Search a 2D Matrix
Search in Rotated Sorted Array
Search in Rotated Sorted Array II
這類題目基本可以分為如下四種題型:
1. Search Insert Position 和 Search for a Range 是考察二分查詢的基本用法。基本思路是每次取中間,如果等於目標即返回,否則根據大小關係切去一半,因此時間複雜度是O(logn),空間複雜度O(1)。以
int l = 0; int r = A.length-1; while(l<=r) { int mid = (l+r)/2; if(A[mid]==target) return mid; if(A[mid]<target) l = mid+1; else r = mid-1; } return l;
這樣當迴圈停下來時,如果不是正好找到target,l指向的元素恰好大於target,r指向的元素恰好小於target,這裡l和r可能越界,不過如果越界就說明大於(小於)target並且是最大(最小)。
Search for a Range
這道題能更好的解釋這一點。其思路是先用二分查詢找到其中一個target,然後再往左右找到target的邊緣。我們主要看找邊緣(往後找)的程式碼:
int newL = m; int newR = A.length-1; while(newL<=newR) { int newM=(newL+newR)/2; if(A[newM]==target) { newL = newM+1; } else { newR = newM-1; } } res[1]=newR;
我們的目標是在後面找到target的右邊界,因為左邊界已經等於target,所以判斷條件是相等則向右看,大於則向左看,根據上面說的,迴圈停下來時,l指向的元素應該恰好大於target,r指向的元素應該等於target,所以此時的r正是我們想要的。向前找邊緣也同理。
2. Sqrt(x)是數值處理的題目,但同時也可以用二分查詢的思想來解決。因為我們知道結果的範圍,取定左界和右界,然後每次砍掉不滿足條件的一半,直到左界和右界相遇。演算法的時間複雜度是O(logx),空間複雜度是O(1)。這裡同樣是考察二分查詢的基本用法。程式碼如下:
public int sqrt(int x) { if(x<0) return -1; if(x==0) return 0; int l=1; int r=x/2+1; while(l<=r) { int m = (l+r)/2; if(m<=x/m && x/(m+1)<m+1) return m; if(x/m<m) { r = m-1; } else { l = m+1; } } return 0;}
這裡要注意,這裡判斷相等的條件不是簡單的 m == x/m, 而是 m<=x/m && x/(m+1)<m+1, 這是因為輸出是整型,sqrt(14)=3 但 3 != 14/3. 所以我們需要一個範圍框住結果。另外根據二分查詢演算法的特性,如果不能正好m==x/m停下,那麼r指向的數字將正好是結果取整的值。所以我們也可以這樣寫:
public int sqrt(int x) { if(x<0) return -1; if(x==0) return 0; int l=1; int r=x/2+1; while(l<=r) { int m = (l+r)/2; if(m==x/m ) return m; if(x/m<m) { r = m-1; } else { l = m+1; } } return r;}
3.
Search a 2D Matrix是二分查詢演算法的多維應用,通過觀察不難發現,輸入的矩陣行內有序並且行間有序,所以查詢只需要先按行查詢,定位出在哪一行之後再進行列查詢即可,兩次二分查詢,時間複雜度是O(logm+logn),空間上只需兩個輔助變數,因而是O(1),這裡不再贅述。
4. Search in Rotated Sorted Array和 Search in Rotated Sorted Array II算是二分查詢演算法的一個變體。在 Search in Rotated Sorted Array中,乍一看感覺陣列已經不是有序的了,也就無法用二分查詢演算法,但仔細分析一下會發現,因為只rotate了一次,如果二分一下,總有一半是有序的,而且和另一半無區間重疊,我們只需要檢查有序的一半的前後兩個元素就可以確定target可能在哪一半。具體來說,假設陣列是A,每次左邊緣為l,右邊緣為r,還有中間位置是m。在每次迭代中,分三種情況:
(1)如果target==A[m],那麼m就是我們要的結果,直接返回;
(2)如果A[m]<A[r],那麼說明從m到r一定是有序的(沒有受到rotate的影響),那麼我們只需要判斷target是不是在m到r之間,如果是則把左邊緣移到m+1,否則就target在另一半,即把右邊緣移到m-1。
(3)如果A[m]>=A[r],那麼說明從l到m一定是有序的,同樣只需要判斷target是否在這個範圍內,相應的移動邊緣即可。
根據以上方法,每次我們都可以切掉一半的資料,所以演算法的時間複雜度是O(logn),空間複雜度是O(1)。 Search in Rotated Sorted Array II中array有重複元素,按照剛剛的思路,二分之後雖然一半是有序的,但我們會遇到中間和邊緣相等的情況,我們就丟失了哪邊有序的資訊,因為哪邊都有可能是有序的結果。假設原陣列是{1,2,3,3,3,3,3},那麼旋轉之後有可能是{3,3,3,3,3,1,2},或者{3,1,2,3,3,3,3},這樣的我們判斷左邊緣和中心的時候都是3,如果我們要尋找1或者2,我們並不知道應該跳向哪一半。解決的辦法只能是對邊緣移動一步,直到邊緣和中間不在相等或者相遇,這就導致了會有不能切去一半的可能。所以最壞情況(比如全部都是一個元素,或者只有一個元素不同於其他元素,而他就在最後一個)就會出現每次移動一步,總共是n步,演算法的時間複雜度變成O(n)。
總體來說,二分查詢演算法理解起來並不算難,但在實際面試的過程中可能會出現各種變體,如何靈活的運用才是制勝的關鍵。我們要抓住“有序”的特點,一旦發現輸入有“有序”的特點,我們就可以考慮是否可以運用二分查詢演算法來解決該問題。