输入关键词开始搜索

二分查找与变体

经典二分查找

// 在有序数组中查找 target,返回索引,不存在返回 -1
int binarySearch(const vector<int> &arr, int target) {
    int lo = 0, hi = arr.size() - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;  // 防溢出
        if (arr[mid] == target) return mid;
        else if (arr[mid] < target) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}
// 时间 O(log n),空间 O(1)

lower_bound / upper_bound

// 第一个 >= target 的位置
int lowerBound(const vector<int> &arr, int target) {
    int lo = 0, hi = arr.size();
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (arr[mid] < target) lo = mid + 1;
        else hi = mid;
    }
    return lo;  // 可能 == arr.size()(没找到)
}

// 第一个 > target 的位置
int upperBound(const vector<int> &arr, int target) {
    int lo = 0, hi = arr.size();
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (arr[mid] <= target) lo = mid + 1;
        else hi = mid;
    }
    return lo;
}

// 使用示例
vector<int> arr = {1, 2, 2, 2, 3, 4};
lowerBound(arr, 2);  // 1  (第一个 2)
upperBound(arr, 2);  // 4  (3 的位置)
// 元素 2 的个数 = upperBound - lowerBound = 3

边界模板速查

目标条件lo 更新hi 更新返回值
第一个 >= targetarr[mid] < targetmid+1midlo
第一个 > targetarr[mid] <= targetmid+1midlo
最后一个 < targetarr[mid] < targetmidmid-1hi
最后一个 <= targetarr[mid] <= targetmidmid-1hi

记忆口诀:左闭右开 [lo, hi),缩左用 mid+1,缩右用 mid

旋转数组查找

// [4,5,6,7,0,1,2] 中查找 target
int searchRotated(const vector<int> &arr, int target) {
    int lo = 0, hi = arr.size() - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (arr[mid] == target) return mid;

        if (arr[lo] <= arr[mid]) {  // 左半有序
            if (arr[lo] <= target && target < arr[mid]) hi = mid - 1;
            else lo = mid + 1;
        } else {                     // 右半有序
            if (arr[mid] < target && target <= arr[hi]) lo = mid + 1;
            else hi = mid - 1;
        }
    }
    return -1;
}

二分答案

// 场景:找满足条件的最小值 / 最大值
// 例:n 本书分给 m 个人,最小化最大阅读量
bool canSplit(const vector<int> &pages, int m, int maxLoad) {
    int cnt = 1, sum = 0;
    for (int p : pages) {
        if (sum + p > maxLoad) { cnt++; sum = p; }
        else sum += p;
    }
    return cnt <= m;
}

int minMaxLoad(const vector<int> &pages, int m) {
    int lo = *max_element(pages.begin(), pages.end());
    int hi = accumulate(pages.begin(), pages.end(), 0);
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (canSplit(pages, m, mid)) hi = mid;
        else lo = mid + 1;
    }
    return lo;
}

常见陷阱

// 陷阱 1: mid 溢出
int mid = (lo + hi) / 2;           // ❌ lo+hi 可能溢出
int mid = lo + (hi - lo) / 2;      // ✅

// 陷阱 2: 死循环
while (lo < hi) {
    int mid = lo + (hi - lo) / 2;
    if (condition) lo = mid;       // ❌ lo 不前进 → 死循环
    // 修正:lo = mid + 1;
}

// 陷阱 3: 边界条件
// lo <= hi   ← 找精确值
// lo < hi    ← 找边界/位置