二分搜索及常见变形

359 阅读2分钟

基本思想

二分搜索是基于分治的算法,该算法的基本思想为每次都把数据分成左右两部分,一部分含有要查找的数据,另一部分不包含要查找的数据。

最常见的使用场景为:在排序的数组中查找特定元素的索引。

  • 最差复杂性: O(log n)
  • 平均复杂性: O(log n)
  • 最优复杂性: O(1)
  • 空间复杂性: O(1)

常规二分搜索

此处有几处需要注意的事项:

  1. while循环的等号不可以省略,是防止当数据仅有一个时,例如nums = {1}; target = 1能够正确找到对应的索引;

  2. 如果要查找的数据中有多个target,该方法只会找到其中任意的一个target,视数据情况而定,如需要特定查找左边或者右边可以使用下边的变形二分查找。

  3. 计算mid时,left + right可能会超过int的最大值,此处可以采用left + (right - left) / 2防止越界,因为:left + (right - left) / 2 = left - left / 2 + right / 2 = (left + right) / 2

  4. 如所给数据中不包含target,返回的结果为所给数据中应在的位置的索引。

int binary_search(int nums[], int target, int left, int right) {
    while (left <= right) {
        int mid = (left + right) >> 1; // 右移一位相当于除以2
        if (nums[mid] < target) left = mid + 1; // 要查找的数据在mid的右边
        else if (nums[mid] > target) right = mid - 1; // 要查找的数据在mid的左边
        else return mid; // 找到了改元素,返回索引
    }
    return -1;
}

力扣题目

35. 搜索插入位置

变形二分搜索

左右二分查找

在常规的二分查找中,如果有多个target,查找结果与所给数据相关。因此,如果我们想要target中最左边或者最右边的索引,即可使用下边的变形二分查找。当left = right时,结束循环。

查找最右边元素的索引

找到小于等于 target 的最大的元素的索引。

注意 : 此处计算mid时需要加1,如果不加1,当left = right - 1时,除法为向下取整,mid = (left + right) / 2 = left,如nums[mid] <= target成立,下次mid仍然left,则会造成死循环

int right(int nums[], int target, int left, int right) {
    while (left < right) {
        int mid = (left + right + 1) >> 1;
        if (nums[mid] <= target) left = mid;
        else right = mid - 1;
    }
    return left;
}

查找最左边元素的索引

找到大于等于 target 的最小的元素的索引。

int left(int nums[], int target, int left, int right) {
    while (left < right) {
        int mid = (left + right) >> 1;
        if (nums[mid] >= target) right = mid;
        else left = mid + 1;
    }
    return left;
}

上边两个查找都可以去掉等号,变为找小于 target 的最大元素的索引(大于 target 的最小元素的索引)。

#include <bits/stdc++.h>

using namespace std;

int left(vector<int> &nums, int t, int l, int r) {
    while (l < r) {
        int mid = (l + r) >> 1;
        if (nums[mid] >= t) r = mid;
        else l = mid + 1;
    }
    return l;
}

int right(vector<int> &nums, int t, int l, int r) {
    while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (nums[mid] <= t) l = mid;
        else r = mid - 1;
    }
    return l;
}

int upper(vector<int> &nums, int t, int l, int r) {
    while (l < r) {
        int mid = (l + r) >> 1;
        if (nums[mid] <= t) l = mid + 1;
        else r = mid;
    }
    return l;
}

int main() {
    vector<int> nums = {1, 2, 3, 3, 4, 5, 6};
    // 此处可以不减一,如不减,当查找7时,结果为size
    int size = nums.size() - 1;

    cout << left(nums, 7, 0, size) << endl;
    cout << right(nums, 7, 0, size) << endl;
    // 与STL库中upper_bound效果一致
    cout << upper(nums, 7, 0, size) << endl;

    cout << lower_bound(nums.begin(), nums.end(), 7) - nums.begin() << endl;
    cout << upper_bound(nums.begin(), nums.end(), 7) - nums.begin() << endl;
    return 0;
}

力扣题目

34. 在排序数组中查找元素的第一个和最后一个位置