算法基础之二分及二分答案

二分的时间复杂度

二分查找的平均时间复杂度和最坏时间复杂度均为o(logn)

二分的本质是什么?

二分的本质并不是单调性,二分的本质是边界。

数据结构书上面的代码

#include<bits/stdc++.h>
using namespace std;
const int N  = 1010;
int a[N];
int n;
int BinarySearch( int x, int &cnt)
{
    int l = 0, r = n - 1;
    while(l <= r){
        cnt++;
        int mid = (l + r) >> 1;
        if(a[mid] > x) r = mid - 1;
        else if(a[mid] < x) l = mid + 1;
        else return mid;
    }
    return -1;
}
int main()
{
    cin >> n;
    for(int i=0;i<n;i++) cin >> a[i];
    int x;cin >> x;
    int cnt = 0;
    int indix = BinarySearch(x,cnt);
    cout<<indix<<'\n'<<cnt;
    return 0;
}

书上代码和接下来的代码的不同

书上面的代码只是用于确切的查找某一个元素是在数组里面的下标

而接下来给出的代码模板是用于查找分界点。也就是用来找到数组中第一个等于target的元素的位置。这个模板确保即使存在多个相同的值,也能返回最左边的那个,也就是第一个出现的位置。或者可以用来查找最后一个等于target的元素的位置。

二个不同的模板:(根据考虑是左边界还是右边界来选择模板)

在解决这种二分查找问题时,重要的是清晰地定义我们要找的目标边界是什么,并明确何时移动左边界l和右边界r

寻找左半区符合性质的右边界点(性质在左半区符合,在右半区不符合)

目标:找到左半区最右侧的符合性质的点;

  • 如果mid点符合性质:说明mid位于左半区,我们需要包含mid继续向右查找,因此更新左边界l = mid
  • 如果mid点不符合性质:说明mid已经位于右半区,所以我们需要向左查找,更新右边界r = mid - 1。(不能包含mid,mid在右半部分,肯定不符合性质,所以要从r-1开始查找边界
寻找右半区符合性质的左边界点(性质在右半区符合,在左半区不符合)

目标:找到右半区最左侧的符合性质的点

  • 如果mid点符合性质:说明mid位于右半区,我们需要包含mid继续向左查找,因此更新右边界r = mid
  • 如果mid点不符合性质:说明mid位于左半区,所以我们需要向右查找,更新左边界l = mid + 1

为什么对于第一个模板需要(l+r+1)/2呢?

当l=r - 1的时候,如果mid =(l+r)/2,此时mid = l,如果check是true,那就将l = mid = l ,那么就会进入死循环(l < r,while循环无法结束)

当mid=(l+r+1)/2,此时,mid = r,如果check为true,l = mid = r,此时while循环结束

代码示例,没有写main函数,只有大致模板

#include <iostream>

using namespace std;

bool check()
{
    //根据特定题目来写
}
//寻找左半区符合性质的右边界点
int findRightBoundary(vector<int> nums, int target) {
    int l = 0, r = nums.size() - 1;
    while (l < r) {
        int mid = (l + r + 1 )/ 2; //直接取平均可能会溢出,推荐 l + (( r - l + 1 ) >> 1)
        if (check(nums[mid])) {  // check函数用来判断当前mid位置是否符合性质
            l = mid;
        } else {
            r = mid - 1;
        }
    }
    return l;
}
//寻找右半区符合性质的左边界点
int findLeftBoundary(vector<int> nums, int target) {
    int l = 0, r = nums.size() - 1;
    while (l < r) {
        int mid = (l + r )/ 2;  // 向下取整
        if (check(nums[mid])) {
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    return l;
}

例题:l​​AcWing 789. 数的范围 - AcWingicon-default.png?t=N7T8https://www.acwing.com/problem/content/791/

P1678 烦恼的高考志愿 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1678

题目讲解 :CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/gege_0606/article/details/139043517?spm=1001.2014.3001.5502

二分答案

二分答案和整数二分的不同:二分答案枚举的是答案,整数枚举枚举的是下标。

P2440 木材加工 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P2440

例题 砍树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1873相应题解查看

二分答案练习题--砍树-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/gege_0606/article/details/139044090?spm=1001.2014.3001.5501

STL 的二分查找(c++)

low_bound和upper_bound都要保证元素有序

low_bound

low_bound是返回一个迭代器,指向在范围内第一个不小于(大于或等于)给定值的元素

如果没有找到,那么就返回末尾的迭代器。

语法: auto it = lower_bound(start, end, value)

以下代码返回的是不小于4 的下标

low_bound(v.begin(),v.end(),4)-v.begin();

upper_bound

upper_bound返回一个迭代器,指向在范围内第一个大于给定值的元素。

如果没有找到,那么就返回末尾的迭代器。

语法: auto it = upper_bound(start, end, value)

用法,low_bound和up_bound可以用来快速计算某个值出现的次数,如果计算元素4在vector v出现的次数,代码如下:

count = upper_bound(v.begin(),v.end(),4) - low_bound(v.begin(),v.end(),4);

练习题P1102 A-B 数对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1102题解如下:二分练习题-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/gege_0606/article/details/139043517?spm=1001.2014.3001.5502 

  • 34
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值