二分查找(Binary Search)是計算機科學中最基礎、最高效的查找算法之一,時間複雜度 O(log n),空間 O(1)。雖然思想簡單,但邊界條件處理是 Bug 溫床(著名的「90% 程式員寫不對二分查找」)。本文從原理推導、標準模板、變體應用、常見陷阱四個維度,助你徹底掌握二分查找。
int binarySearch(const vector<int>& arr, int target) {
int lo = 0, hi = (int)arr.size() - 1; // 閉區間 [lo, hi]
while (lo <= hi) {
int mid = lo + (hi - lo) / 2; // 防溢出寫法
if (arr[mid] == target) return mid;
if (arr[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return -1; // 未找到
}
lo <= hi,區間為空時 lo > hi 退出。lo + (hi - lo) / 2 避免 lo + hi 溢出(雖現代 64 位系統較少見)。lo = mid + 1 / hi = mid - 1,確保區間嚴格縮小,避免死循環。| 變體 | 目標 | 返回值 | 核心修改 |
|---|---|---|---|
| 查找第一個 ≥ target | 下界 | 索引 | if (arr[mid] >= target) hi = mid - 1; else lo = mid + 1; 循環後 lo 即答案 |
| 查找最後一個 ≤ target | 上界 | 索引 | if (arr[mid] <= target) lo = mid + 1; else hi = mid - 1; 循環後 hi 即答案 |
| 查找第一個 > target | 上界嚴格 | 索引 | 同下界,條件改 > |
| 查找最後一個 < target | 下界嚴格 | 索引 | 同上界,條件改 < |
int lowerBound(const vector<int>& arr, int target) {
int lo = 0, hi = arr.size(); // hi = n,不減 1
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (arr[mid] < target) lo = mid + 1;
else hi = mid;
}
return lo; // 第一个 >= target 的位置,可能為 n
}
優勢:lo < hi 循環條件、hi = mid 不用 -1、退出時 lo == hi 即答案,極難出錯。
lo + (hi - lo) / 2 或 (lo + hi) >>> 1(無符號右移)。lo = mid 或 hi = mid,需配合 lo < hi 且 mid 取下中位((lo+hi)/2)或上中位((lo+hi+1)/2)。hi = 0 或 1,循環正常執行)。==,引入 eps 或用整數放大比較。二分不限於陣列索引,任何單調謂詞 P(x):「x 滿足條件」隨 x 遞增從 false 變 true(或反過來),均可二分答案。
sqrt(x)(整數部分)int mySqrt(int x) {
if (x < 2) return x;
long lo = 1, hi = x / 2; // 答案在 [1, x/2]
while (lo <= hi) {
long mid = lo + (hi - lo) / 2;
if (mid * mid <= x) lo = mid + 1;
else hi = mid - 1;
}
return hi; // 最後一個 mid*mid <= x 的值
}
熟練掌握上述模板與變體,二分查找將成為你手中最銳利、最可靠的算法利器。