二分搜索專題

二分搜索基礎知識

 

一、二分搜索常見的應用場景

1、在有序序列中查找一個數

arr: ... .... ...mid ... ... ... n

2、並不是非要在有序序列中才能得到應用

 

二、二分搜索常見的考察點

1、對於邊界條件的考察以及代碼實現的能力。

 

三、二分搜索常見題目的變化

1、給定處理或查找的對象不同

2、判斷條件不同、

3、要求返回的內容不同

4、在有序循環數組中進行二分搜索。1,2,3,4,5循環之後可以是:1,2,3,4,5或2,3,4,5,1或3,4,5,1,2或4,5,1,2,3或5,1,2,3,4

 

四、二分搜索的注意事項

mid =  (left +right)/2   當數組下標較大的時候容易產生溢出。

更安全的寫法:mid = left + (right - left)/2

 

五、二分搜索的代碼實現

def binary_search(list,key):
    left,right = 0,len(list)
    while left < right:
        mid = left + (right-left)/2
        if list[mid] == key:
            return mid
        elif list[mid] > key:
            right = mid-1
        else:
            left = mid+1
    return -1

 

二分搜索經典案例

 

案例一:

給定一個無序數組arr,已知任意相鄰的兩個元素,值都不重複。請返回任意一個局部最小的位置。

所謂局部最小的位置是指,如果arr[0]<arr[1],那麼位置0就是一個局部最小的位置。如果arr[N-1](也就是arr最右的數)小於arr[N-2],那麼位置N-1也是局部最小的位置。如果位置 i 既不是最左位置也不是最右位置。那麼只要滿足arr[i]同時小於它左右兩側的值即(arr[i-1]和arr[i+1]),那麼位置i也是一個局部最小的位置。

思路:

本題依然可以用二分搜索來實現,時間複雜度爲O(logN)

1、arr爲空或長度爲0,返回-1,表示局部最小位置不存在。

2、如果arr長度爲1,返回0,因爲此時0是局部最小位置。

3、如果arr長度大於1。首先檢查兩頭的位置,考察arr[0]<arr[1] 如果是,直接返回arr[0] ,同樣如果arr[n-1]<arr[n-2],直接返回arr[n-1],如果兩頭的位置都不是局部最小,那麼考察中間位置mid的情況,如果mid的值既小於左邊第一個值,又小於右邊第一個值。那麼此時mid就是局部最小的值,返回即可。如果mid比左邊的小,而比右邊的大,那麼在mid的左邊一定存在局部最小的情況,此時可以直接對左部分進行同樣邏輯的二分搜索。反之在mid的右邊一定存在局部最小的情況,對右部分繼續進行同樣邏輯的二分搜索。如果mid位置的數比左右都大,說明左右兩邊都存在局部最小的情況。

 

案例二:

給定一個有序數組arr,在給定一個整數num,請在arr中找到num這個數出現的最左邊的位置。

思路:

這裏我們以數組[1,2,3,3,3,3,4,4,4,4,4,4]爲例,num = 3

1、先生成一個全局變量res,用來記錄最後一次找到3的位置。初始時res = -1.

2、首先找到中間的數,如果>3,則在左部分進行二分搜索,此時找到了3,將res的值更新爲3,

3、找到3之後,繼續對左部分進行二分搜索,接下來找到中間的數2,接着對右部分進行二分搜索,找到3,此時就是我們要的值,更新res的值,爲2,返回2。

->

 

案例三:

給定一個有序數組arr,返回arr中的最小值。有序循環數組是指,有序數組左邊任意長度的部分放到右邊去,右邊的部分拿到左邊來。比如數組[1,2,3,3,4],是有序循環數組,[4,1,2,3,3]也是。

思路:

1、假設我們在數組arr的L到R之間找最小值,如果arr[L] < arr[R],說明這個數組整體有序,最小值自然是最左邊的arr(L),返回arr(L)即可。

2、如果arr[L] >= arr[R],說明中間可能包含循環的部分,比如數組[2,2,3,1,2]我們考慮L到R的處於中間位置M的數,

3、如果發現arr(L)>arr(M),說明最小值只可能出現在L到M的範圍上,因爲只有arr[M]是循環過的部分時,纔有可能出現arr[L]>arr[M]的情況。比如數組[7,8,9,1,2,3,4,5,6],arr[L] = 7 ,arr[M] = 2 , arr[R] = 6,最小值1依舊在左半部分,此時在左部分繼續進行二分搜索。

4、如果發現arr[M]>arr[R],說明最小值只可能出現在右半部分,比如說數組[4,5,6,7,8,9,1,2,3],最小值1出現在右部分,此時在右部分就繼續進行二分搜索。

5、如果arr[L]>arr[M]和arr[M]>arr[R]這兩個條件都不滿足,即arr[L]<=arr[M]和arr[M]<=arr[R],說明三值相等,我們無法繼續用二分搜索實現尋找最小值,比如數組[2,2 ... ... 2,1,2 ... ... 2,2。只能通過遍歷的方式在arr[L]和arr[M]上尋找最小值。

 

案例四:

給定一個有序數組arr,其中不含有重複元素,請找到滿足arr[i] == i條件的最左的位置。如果所有位置上的數都不滿足條件,返回-1.

思路:

1、首先生成全局變量res,初始值爲-1,如果arr的長度爲N。

2、初始時現考慮arr[0]的值,如果arr[0]>N-1,因爲整個數組有序,所以整個數組上的值都>N-1,不會存在arr[i] = i 的情況。直接返回-1,同理我們要考慮arr[n-1]<0的情況,如果成立,也直接返回-1。

3、如果上面兩種情況都沒有發生,我們就考察中間位置的數M情況,如果arr[m]>m,所以m到n上都不會出現arr[i] = i 情況,我們繼續對0到m-1的情況上進行二分就可以了。

4、如果arr[m]<m,因爲數組即沒有重複值,同時又是遞增,所以從m位置到0位置上的值是從右到左是遞減的,而遞減量最小爲1,而位置的值,從最右到最左遞減量是嚴格的1,所以整個0到m上都不會出現arr[i] = i的情況,那麼此時之用在m+1到n的情況下進行二分搜索就可以了。

5、如果arr[m] = m,用res記錄下當前的值,但同時我們要找到的是最左的位置,所以依然是0到m的範圍上進行二分搜索。

->

 

案例五:

給定一棵完全二叉樹的頭節點head,返回這棵樹的節點個數。如果完全二叉樹的節點樹爲N,請實現時間複雜度低於O(N)的解法。

思路:

這道題看似和二分搜索沒有關係,實質上最優解就是來自與二分搜索,完全二叉樹節點的增加和減少和堆類似。利用它的性質,是的我們使用二分搜索統計它的個數成爲了可能。

1、首先找到二叉樹最左邊的節點,最左邊的節點一定是在二叉樹的最後一層,我們可以通過它來統計二叉樹的高度,得到層數爲h,

2、然後我們找到二叉樹頭節點的右子樹的最左邊的節點,如果能到達最後一層,那麼說明頭節點的左子樹一定是一棵滿二叉樹,我們可以直接根據滿二叉樹的計算公式算出左子樹的節點個數,加上頭節點,剩下的節點個數我們可以通過遞歸的方法用相同的方式在右子樹上求出節點個數。

->

3、如果頭節點的右子樹的最左節點不能到達最後一層,那此時頭節點的右子樹一定是比左子樹少一層的滿二叉樹,我們可以直接根據滿二叉樹的計算公式算出右子樹的節點個數,加上頭節點,剩下的節點個數我們可以用相同的方法遞歸的求出左子樹的界定個數。

->

如果使用普通遍歷的方式,時間複雜度爲O(N)

最優解的過程,時間複雜度爲O(logH^2),H爲這顆完全二叉樹的高度。

 

案例六:

如果更快的求一個整數k的N次方。如果兩個整數相乘並得到結果的時間複雜度爲O(1),得到整數k的N次方的過程請實現時間複雜度爲O(logN)的方法。

普通的做法是K^n = K*K........*K*K(n個K),乘了n-1次,時間複雜度爲O(N)。

但其實是可以在時間複雜度爲O(logN)實現的,這裏我們舉個例子來說。

思路:

10^75 = 10^1001011(75的二進制表達式)

         = 10^64 * 10^8 * 10^2 * 10^1

         = 10^1000000  *  10^1000 * 10^10  *  10^1

 

 

10^64 10^32 10^16 10^8 10^4 10^2 10^1
1 0 0 1 0 1 1

 

n的二進制可以天然的把我們的乘法過程劃分的十分優良,這一思想也是來自於二分搜索。

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章