二分查找,找到左右端点

二分查找,找到左右端点

1. 简单二分查找

这里的简单,指的是,数组中没有重复元素,并且查找的值一定在数组中。

可以有以下两种写法👇

// 普通find函数(不允许有重复值)
int bi_find1(vector<int>& nums, int t) {
    int l = 0, r = nums.size() - 1;
    int mid;
    while (l < r) {
        mid = l + ((r - l) >> 1);
        if (nums[mid] <= t) l = mid;
        else r = mid - 1;
    }
    return l;
}

// 普通find函数(可以有重复值 —— 如果有重复返回的是最左边界)
int bi_find2(vector<int>& nums, int t) {
    int l = 0, r = nums.size() - 1;
    int mid;
    while (l < r) {
        mid = l + ((r - l) >> 1);
        if (nums[mid] >= t) r = mid;
        else l = mid + 1;
    }
    return l;
}

其中l + ((r - l) >> 1); 是为了防止溢出。

为什么写while(l < r) , 而不是while(l <= r)—— 防止死循环,此外,还有好处是,每次循环结束的条件,其实是l == r,也就是最终l = r,就不用考虑返回值到底是l还是r了。

2. 找到左右端点

这里的二分查找找到左右端点,指的是,数组中有重复值,找到第一次和最后一次出现的位置。

主要的问题是:

  • 使用if (nums[mid] <= t) l = mid; else r = mid - 1;(即bi_find1()的核心部分)

  • 还是if (nums[mid] >= t) r = mid; else l = mid + 1;(即bi_find2()的核心部分)。

这里先不给出代码,容易看懵。解决方案,举例(举例的时候,只举用两个数字的数组进行举例就行——保证正确)。

如图所示,nums[] = {9, 9}; l = 0, r = 1;那么mid = 0;

如果是找左边界,我们显然需要r左移(因为结束条件是l == r,因此此时应该是l保持不变,r左移),因此此时是对r赋值。—— 即使用if (nums[mid] >= t) r = mid;

如果是找右边界,我们显然需要l 右移,因此此时是对l赋值。—— —— 即使用if (nums[mid] <= t) l = mid;。—— 但是右边界还有一个问题,可以发现,l永远移动不到r上,因此我们取mid的时候,可以用mid = (l + r + 1) >> 2;

image-20220808230956738

左右边界代码👇

// 找到最左边界(同时,如果t不存在,那么返回的是t应该放到的位置)
int bi_find_left(vector<int>& nums, int t) {
    int l = 0, r = nums.size() - 1;
    int mid;
    while (l < r) {
        mid = l + ((r - l) >> 1);
        if (nums[mid] >= t) r = mid;
        else l = mid + 1;
    }
    return l;
}

// 找到最右边界
int bi_find_right(vector<int>& nums, int t) {
    int l = 0, r = nums.size() - 1;
    int mid;
    while (l < r) {
        mid = (r + l + 1) >> 1;
        if (nums[mid] <= t) l = mid;
        else r = mid - 1;
    }
    return l;
}

3. 找到应该插入到的位置

使用普通的二分查找就可以。

4. 代码示例

4.1 代码

// g++ bi_find.cpp -o bi_find
// ./bi_find
#include <bits/stdc++.h>

using namespace std;

// 普通find函数(不允许有重复值)
int bi_find1(vector<int>& nums, int t) {
    int l = 0, r = nums.size() - 1;
    int mid;
    while (l < r) {
        mid = l + ((r - l) >> 1);
        if (nums[mid] <= t) l = mid;
        else r = mid - 1;
    }
    return l;
}

// 普通find函数(可以有重复值 —— 如果有重复返回的是最左边界)
int bi_find2(vector<int>& nums, int t) {
    int l = 0, r = nums.size() - 1;
    int mid;
    while (l < r) {
        mid = l + ((r - l) >> 1);
        if (nums[mid] >= t) r = mid;
        else l = mid + 1;
    }
    return l;
}

// 找到最左边界(同时,如果t不存在,那么返回的是t应该放到的位置)
int bi_find_left(vector<int>& nums, int t) {
    int l = 0, r = nums.size() - 1;
    int mid;
    while (l < r) {
        mid = l + ((r - l) >> 1);
        if (nums[mid] >= t) r = mid;
        else l = mid + 1;
    }
    return l;
}

// 找到最右边界
int bi_find_right(vector<int>& nums, int t) {
    int l = 0, r = nums.size() - 1;
    int mid;
    while (l < r) {
        mid = (r + l + 1) >> 1;
        if (nums[mid] <= t) l = mid;
        else r = mid - 1;
    }
    return l;
}

int main() {
    vector<int> nums = {0, 1, 4, 6, 6, 6, 10, 92, 100};
    
    // 找到单一元素出现的位置
    cout << "bi_find1() pos = " << bi_find1(nums, 1) << endl; // 1
    cout << "bi_find2() pos = " << bi_find2(nums, 1) << endl; // 1

    // 第一次出现的位置
    cout << endl;
    cout << "bi_find_left() pos = " << bi_find_left(nums, 1) << endl; // 1
    cout << "bi_find_left() pos = " << bi_find_left(nums, 6) << endl; // 3


    // 最后一次出现的位置
    cout << endl;
    cout << "bi_find_right() pos = " << bi_find_right(nums, 1) << endl; // 1
    cout << "bi_find_right() pos = " << bi_find_right(nums, 6) << endl; // 5


    // 找到不存在的元素的位置
    cout << endl;
    cout << "bi_find1() pos = " << bi_find1(nums, 2) << endl; // 1
    cout << "bi_find2() pos = " << bi_find2(nums, 2) << endl; // 2

    exit(0);
}

4.2 运行方法(Linux下)

上面代码保存到bi_find.cpp然后使用下面方法编译运行。

g++ bi_find.cpp -o bi_find
./bi_find

4.3 运行结果

levi@LEVI1:~/code$ g++ bi_find.cpp -o bi_find
levi@LEVI1:~/code$ ./bi_find 
bi_find1() pos = 1
bi_find2() pos = 1

bi_find_left() pos = 1
bi_find_left() pos = 3

bi_find_right() pos = 1
bi_find_right() pos = 5

bi_find1() pos = 1
bi_find2() pos = 2

ode$ ./bi_find
bi_find1() pos = 1
bi_find2() pos = 1

bi_find_left() pos = 1
bi_find_left() pos = 3

bi_find_right() pos = 1
bi_find_right() pos = 5

bi_find1() pos = 1
bi_find2() pos = 2


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值