介紹#
二分搜尋的本質是:在一個單調的條件上,找到從 False 變成 True 的那個分界點。
index: 0 1 2 3 4 5
條件: False False False True True True
↑ 要找的位置
兩種常見的用法:
- 在已排序的陣列上找位置:例如第一個
>= x的 index。 - 對答案二分:答案的範圍很大,但可以寫一個
check(x)判斷「答案能不能是x」,而且check是單調的。
模板一:lower bound / upper bound(左閉右開)#
我固定用左閉右開 [lo, hi),搜尋結束時 lo == hi,就是答案的位置:
def lower_bound(arr, x): # 第一個 >= x 的 index
lo, hi = 0, len(arr) # hi 是 len(arr),不是 len(arr) - 1
while lo < hi:
mid = (lo + hi) // 2
if arr[mid] < x:
lo = mid + 1
else:
hi = mid
return lo
def upper_bound(arr, x): # 第一個 > x 的 index
lo, hi = 0, len(arr)
while lo < hi:
mid = (lo + hi) // 2
if arr[mid] <= x:
lo = mid + 1
else:
hi = mid
return lo
Python 內建的 bisect.bisect_left 和 bisect.bisect_right 就是這兩個函式。
主要參數說明:#
hi = len(arr):所有元素都比x小時,答案是len(arr),所以hi必須能等於它(LC 3508)。mid = (lo + hi) // 2要加括號:寫成lo + hi // 2會先算hi // 2(LC 3709)。- 區間
[a, b]內的個數 =upper_bound(b) - lower_bound(a)。
模板二:對答案二分#
找「讓 check(x) 成立的最小 x」:
lo, hi = 最小可能的答案, 最大可能的答案
while lo < hi:
mid = (lo + hi) // 2
if check(mid):
hi = mid # mid 可行,答案在 mid 或更左邊
else:
lo = mid + 1 # mid 不可行,答案在右邊
return lo
主要參數說明:#
- 確認
check是單調的:x可行時,所有比x大的也都可行。 - 上界要夠大:LC 3824 一開始把上界設成
max(nums),但答案可能超過它。 - 找最大值時反過來:
check(mid)成立就lo = mid,這時mid要改成(lo + hi + 1) // 2,不然會無限迴圈。或者換成「找第一個不成立的位置再減 1」。
模板三:第 k 小,用「≤ mid 的有幾個」二分#
不直接找第 k 小的數,而是對答案二分:第一個讓「<= mid 的數量 >= k」成立的 mid,就是第 k 小。
lo, hi = 最小值, 最大值
while lo < hi:
mid = (lo + hi) // 2
if count_at_most(mid) < k:
lo = mid + 1
else:
hi = mid
return lo
LC 719 的 count_at_most 用滑動窗口在 O(n) 內數出「距離 <= mid 的 pair 數」,總共 O(n log W)。
模板四:整數開 k 次方根#
x ** (1 / k) 是浮點數運算,會有誤差。要精確的 floor(n^(1/k)),用整數二分:
def kth_root_floor(n, k): # 最大的 x,使得 x ** k <= n
lo, hi = 0, n + 1
while lo < hi:
mid = (lo + hi) // 2
if mid ** k <= n:
lo = mid + 1
else:
hi = mid
return lo - 1
LC 3932 數 [l, r] 內的完全 k 次方數,答案就是 kth_root_floor(r) - kth_root_floor(l - 1)。
模板五:排序 + 前綴和 + bisect#
很多「查詢一個範圍內的總和或個數」的題目,可以先排序、建前綴和,每次查詢用 bisect 找邊界:
pairs = sorted(zip(keys, values))
sorted_keys = [k for k, _ in pairs]
prefix = [0]
for _, v in pairs:
prefix.append(prefix[-1] + v)
def query(limit): # key <= limit 的 value 總和
return prefix[bisect.bisect_right(sorted_keys, limit)]
LC 2838、2300、3709 都是這個模式。
模板六:旋轉陣列#
切在 mid,左右兩半至少有一邊是排序好的,先判斷哪一邊有序,再看目標在不在那一邊:
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return mid
if nums[l] <= nums[mid]: # 左半有序
if nums[l] <= target < nums[mid]:
r = mid - 1
else:
l = mid + 1
else: # 右半有序
if nums[mid] < target <= nums[r]:
l = mid + 1
else:
r = mid - 1
- 搜尋(LC 33):上面的程式;有重複值(LC 81)時
nums[l] == nums[mid]分不出哪邊有序,只能l += 1。 - 找最小值(LC 153):拿
nums[mid]跟nums[r]比,比較大就l = mid + 1,否則r = mid;有重複值(LC 154)時相等就r -= 1。 - 山峰(LC 162):
nums[mid] < nums[mid + 1]代表右邊一定有山峰。 - 配對中落單的那個(LC 540):在它之前每一對都從偶數 index 開始。
兩種區間寫法不能混用#
左閉右開 [lo, hi) | 左閉右閉 [lo, hi] | |
|---|---|---|
初始 hi | len(arr) | len(arr) - 1 |
| 迴圈條件 | while lo < hi | while lo <= hi |
| 往左縮 | hi = mid | hi = mid - 1 |
| 往右縮 | lo = mid + 1 | lo = mid + 1 |
挑一種固定用。 LC 1292 原本用左閉右閉的寫法 AC,改成 while lo < hi 卻沒有同時改 hi = mid - 1,就 WA 了。
複雜度#
- 在長度
n的陣列上二分:O(log n)。 - 對答案二分:
O(log(答案範圍) × check 的成本)。
常見錯誤#
這些都是我練習時實際寫錯過的:
mid = left + right // 2:運算子優先順序,少了括號(LC 3709)。hi = len(arr) - 1卻用左閉右開的迴圈:找不到「插在最後」的位置(LC 3508)。混用兩種區間寫法:只改了迴圈條件,沒改更新方式(LC 1292)。
對答案二分的上界不夠大(LC 3824)。
用浮點數開根號再比較相等:
64 ** (1/3)在 Python 裡是3.9999999999999996,不是4.0(LC 3932)。用浮點數除法算門檻:
success / spell在數字大時可能有誤差,改用整數的ceil(LC 2300)。雙指針的指標往回走:
j_low = min(i + 1, j_low)讓指標每次都被拉回來,變成O(n^2)(LC 1712)。mid = (l + r) / 2:Python 3 的/得到浮點數,不能當 index(LC 704、74、35)。找最小的可行值時,剛好等於就回傳:可能還有更小的也可行(LC 875)。
旋轉陣列只拿
nums[mid]跟目標比:要先判斷哪一半有序(LC 33、81)。每次只移一格:寫成
l += 1/r -= 1等於線性搜尋,TLE(LC 981)。用
start < end判斷有沒有找到:只出現一次時開頭等於結尾(LC 34)。while l < r配l = mid:mid往下取整時l不動,無限迴圈(LC 441、34)。
相關 LeetCode#
LC 2300Successful Pairs of Spells and PotionsLC 2838Maximum Coins Heroes Can CollectLC 3709Design Exam Scores TrackerLC 3508Implement RouterLC 3814Maximum Capacity Within BudgetLC 1712Ways to Split Array Into Three SubarraysLC 3639Minimum Time to Activate StringLC 3824Minimum K to Reduce Array Within LimitLC 1292Maximum Side Length of a Square with Sum Less than or Equal to ThresholdLC 719Find K-th Smallest Pair DistanceLC 3932Count K-th Roots in a RangeLC 327Count of Range SumLC 704Binary SearchLC 35Search Insert PositionLC 34Find First and Last Position of Element in Sorted ArrayLC 74Search a 2D MatrixLC 240Search a 2D Matrix IILC 33Search in Rotated Sorted ArrayLC 81Search in Rotated Sorted Array IILC 153Find Minimum in Rotated Sorted ArrayLC 154Find Minimum in Rotated Sorted Array IILC 162Find Peak ElementLC 875Koko Eating BananasLC 275H-Index IILC 1337The K Weakest Rows in a MatrixLC 268Missing NumberLC 367Valid Perfect SquareLC 69Sqrt(x)LC 888Fair Candy SwapLC 278First Bad VersionLC 374Guess Number Higher or LowerLC 441Arranging CoinsLC 287Find the Duplicate NumberLC 981Time Based Key-Value StoreLC 540Single Element in a Sorted ArrayLC 744Find Smallest Letter Greater Than Target
