二分查詢 三種場景

2021-10-03 15:34:26 字數 4362 閱讀 6155

也稱 折半查詢(binary search)

使用前提:線性表採用順序儲存結構,表中元素按關鍵字有序排列

二分查詢是一種基於比較目標值和陣列中間元素的教科書式演算法。

實現:維護兩個指標left,right,指標之間是搜尋區間

時間複雜度:o(logn)

空間複雜度:o(1)

public int search(int nums, int target) else if(nums[mid] < target)else if(nums[mid] > target)

} return -1;

}

分析搜尋區間while 迴圈的條件中是 <=

因為初始化right的賦值是nums.length - 1,不是nums.length。

前者相當於兩端都閉區間 [left, right],後者相當於左閉右開區間 [left, right),因為索引大小為 nums.length 是越界的。

我們這個演算法中使用的是前者 [left, right] 兩端都閉的區間。這個區間其實就是每次進行搜尋的區間。

什麼時候應該停止搜尋呢?找到了目標值的時候可以終止:

if(nums[mid] == target)

return mid;

但如果沒找到,就需要 while 迴圈終止,然後返回 -1。

那 while 迴圈什麼時候應該終止?

搜尋區間為空的時候應該終止,意味著你沒得找了,就等於沒找到嘛。

while(left <= right) 的終止條件是 left == right + 1,寫成區間的形式就是 [right + 1, right],或者帶個具體的數字進去 [3, 2],可見這時候區間為空,因為沒有數字既大於等於 3 又小於等於 2 的吧。所以這時候 while 迴圈終止是正確的,直接返回 -1 即可。

while(left < right) 的終止條件是 left == right,寫成區間的形式就是 [left, right],或者帶個具體的數字進去 [2, 2],這時候區間非空,還有乙個數 2,但此時 while 迴圈終止了。也就是說這區間 [2, 2] 被漏掉了,索引 2 沒有被搜尋,如果這時候直接返回 -1 就是錯誤的。

如果你非要用 while(left < right) 也可以,我們已經知道了出錯的原因,就打個補丁,處理nums[left] == target的情況:

//...

while(left < right)

return nums[left] == target ? left : -1;

left = mid + 1,right = mid - 1

本演算法的搜尋區間是兩端都閉的,即 [left, right]。

那麼當我們發現索引 mid 不是要找的 target 時,下一步應該去搜尋[left, mid-1] 或者 [mid+1, right] …因為 mid 已經搜尋過,應該從搜尋區間中去除

對於有序陣列 nums = [1,2,2,2,3],target = 2,想得到 target 的左側邊界,即索引 1,也即表示了

int left_bound(int nums, int target)  else if (nums[mid] < target)  else if (nums[mid] > target) 

}return left;

}

小於target的元素個數,此時陣列有重複數時也可以

分析搜尋區間

1、為什麼 while 中是 < 而不是 <=?

用相同的方法分析,因為 right = nums.length 而不是 nums.length - 1。因此每次迴圈的「搜尋區間」是[ left, right ) 左閉右開。

while(left < right) 終止的條件是 left == right,此時搜尋區間 [left, left) 為空,所以可以正確終止。

對於搜尋左右側邊界的二分查詢,寫法普遍是while (left < right)

2、為什麼沒有返回 -1 的操作?如果 nums 中不存在 target 這個值,怎麼辦?

【左側邊界】

對於[1,2,2,2,3],演算法會返回 1。這個 1 的含義可以這樣解讀:nums 中小於 2 的元素有 1 個。

對於 nums = [2,3,5,7],target = 1(比nums都小),演算法會返回 0,含義是:nums 中小於 1 的元素有 0 個。

對於nums = [2,3,5,7],target = 8(比nums都大),演算法會返回 4,含義是:nums 中小於 8 的元素有 4 個。

函式的返回值(即 left 變數的值)取值區間是閉區間 [0, nums.length],所以我們簡單新增兩行**就能在正確的時候 return -1:

while (left < right) 

// target 比所有數都大

if (left == nums.length) return -1;

// 類似之前演算法的處理方式(處理nums[left] == target的情況)

return nums[left] == target ? left : -1;

3、為什麼 left = mid + 1,right = mid ?和之前的演算法不一樣?

答:因為我們的「搜尋區間」是 [left, right) 左閉右開,所以當 nums[mid] 被檢測之後,下一步的搜尋區間應該去掉 mid 分割成兩個區間,即[left, mid) 或 [mid + 1, right)

4、為什麼該演算法能夠搜尋左側邊界?

關鍵在於對於 nums[mid] == target 這種情況的處理:

if (nums[mid] == target)

right = mid;

5、為什麼返回 left 而不是 right?

都是一樣的,因為 while 終止的條件是 left == right。

int right_bound(int nums, int target)  else if (nums[mid] < target)  else if (nums[mid] > target) 

}return left - 1; // 注意

}

1、為什麼這個演算法能夠找到右側邊界?

關鍵點還是這裡:

當 nums[mid] == target 時,不要立即返回,而是增大「搜尋區間」的下界 left,使得區間不斷向右收縮,達到鎖定右側邊界的目的。

if (nums[mid] == target) 

if (left == 0) return -1;

return nums[left-1] == target ? (left-1) : -1;

int mid = 0;

while()

class

solution

(object)

: def search

(self, nums, target)

:"""

:type nums: list[

int]

:type target:

int:rtype:

int"""

if nums == none:

return-1

n =len(nums)

left =

0 right = n -

1 mid =

0while left <= right:

mid = left +

(right - left)/2

if nums[mid]

== target:

return mid

elif nums[mid]

< target:

left = mid +

1 elif nums[mid]

> target:

right = mid -

1return

-1

參考鏈

二分查詢的三種實現

int lower bound int a,int x,int y,int v return x 儘管查詢區間是左閉右開期間 x,y 返回值的候選區卻是閉區間 x,y 當v不存在時,返回的是第乙個比v大的值下標。int upper bound int a,int x,int y,int v retu...

二分查詢的三種模板

思路很簡單,細節是魔鬼。注意 搜尋區間,函式返回值的取值區間。搜尋乙個數,如果存在,返回其索引,否則返回 1。int binarysearch int nums,int target return 1 搜尋區間是閉區間 left,right 迴圈終止條件是 搜素區間為空。因為搜素區間是閉區間,所以迴...

二分查詢的三種形式

題目描述 在不重複有序陣列 公升序 中尋找目標數並返回它的下標,若沒找到返回 1。例子 1,2,3 2 返回 1。見leetcode題 704 上 public intsearch int nums,int target return 1 若完全理解,那麼 中的 可自行替換成 替換後要更換其他部分 ...