定義
二分查找:
思路很簡單,細節是魔鬼。
基本框架:
def find(nums,target):
left = 0
right = ***
while left ** right:
mid = (left + right) // 2
if nums[mid] == target:
***
elif nums[mid] > target:
right = ***
elif nums[mid] < target:
left = ***
return ***
以上框架中的**代表的內容就是細節,需要注意
應用
尋找一個數
def find(nums,target):
left = 0
right = len(nums) - 1 # 注意1
while left <= right: # 注意2
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] > target:
right = mid - 1 # 注意3
elif nums[mid] < target:
left = mid + 1 # 注意4
注意1:
right是數組長度 - 1,用的是左閉右閉的區間。
注意2:
用的是left <= right,即終止條件是left = right + 1。因爲除了找到後結束搜索,還有一種情況是搜索區間爲空,當區間爲[right+1,right]時肯定爲空了;但是如果用的是left < right,即終止條件是left = right ,當區間爲[right,right]時區間並沒有空,還有一個索引沒有判斷。
所以如果用left < right,則是以下代碼:
注意3和注意4:
這個比較好理解。因爲mid這個位置已經搜索過,所以沒必要再搜索。
該算法是有缺陷的,對於所求目標在數組中重複的這種情況,比如[1,2,2,2,3],此時用這個算法返回的索引是2,但是有時候我們想找到target的左側邊界,比如索引1或者是target的右側邊界,比如索引3。
尋找左側邊界
如果要找一個有序數組中小於某個數的個數有多少個,則用以下代碼:
def find(nums,target):
left = 0
right = len(nums)
while left < right:
mid = (left + right) // 2
if nums[mid] == target:
right = mid
elif nums[mid] > target:
right = mid
elif nums[mid] < target:
left = mid + 1
return left # 返回right也可以,因爲left == right
所以在以上代碼上進行改進就能得到某個數在數組中的左側邊界:
def find(nums,target):
left = 0
right = len(nums) # 注意1
while left < right: # 注意2
mid = (left + right) // 2
if nums[mid] == target:
right = mid # 注意3
elif nums[mid] > target:
right = mid # 注意4
elif nums[mid] < target:
left = mid + 1 # 注意5
if left == len(nums):
return -1
return left if nums[left] == target else -1
注意1:
right是數組長度 ,用的是左閉右開的區間。
注意2:
用的是left < right,即終止條件是left = right 。因爲除了找到後結束搜索,還有一種情況是搜索區間爲空,當區間爲[right,right)時肯定已經爲空了。
注意3:
當找到目標時並不是返回已找到,而是要繼續在左側尋找,看是否還有,因爲要得到左側邊界。要切記是左閉右開。
注意4和注意5:
左閉右開,所以左面需要加一,右面不需要,因爲取不上。
尋找右側邊界
def find1(nums,target):
left = 0
right = len(nums)
while left < right:
mid = (left + right) // 2
if nums[mid] == target:
left = mid + 1 # 注意1
elif nums[mid] > target:
right = mid
elif nums[mid] < target:
left = mid + 1
if left == 0:
return -1
return (left -1) if nums[left-1] == target else -1 # 注意2
注意1:
當找到目標時並不是返回已找到,而是要繼續在右側尋找,看是否還有,因爲要得到右側邊界。要切記是左閉右開。
注意2
return left - 1 中,寫成return right - 1 也可以,因爲left = right,爲什麼這樣就能得到右側邊界呢?因爲我們對left的更新是left = mid + 1,所以while循環結束時num[left]一定不等於target了。而 num[left-1]有可能是target。
總結
- 最基本的二分查找算法:
初始化爲 right = len(nums) - 1,所以決定了搜索區間是 [left, right],所以決定了 while (left <= right),同時也決定了 left = mid+1 和 right = mid-1,因爲我們只需找到一個 target 的索引即可
所以當 nums[mid] == target 時可以立即返回。 - 尋找左側邊界的二分查找:
初始化 right = len(nums),所以決定了搜索區間是 [left, right),所以決定了 while (left < right)
同時也決定了 left = mid+1 和 right = mid ,因爲我們需找到 target 的最左側索引,所以當 nums[mid] == target 時不要立即返回,而要收緊右側邊界以鎖定左側邊界。 - 尋找右側邊界的二分查找
因爲我們初始化 right = len(nums),所以決定了我們的搜索區間是 [left, right),所以決定了 while (left < right),同時也決定了 left = mid+1 和 right = mid,因爲我們需找到 target 的最右側索引,所以當 nums[mid] == target 時不要立即返回而要收緊左側邊界以鎖定右側邊界。又因爲收緊左側邊界時必須 left = mid + 1,所以最後無論返回 left 還是 right,必須減一。