算法基础集训(第05天)------>全网最全【二分】万字详解,超多经典例题和拓展题:包括两个神仙模板和各种让你头痛的边界情况,最后还有STL内部封装函数

236de3198d1b418f83f9b1d95bad0be0.gif

目录

 一:二分查找概念定义

 二:整数二分查找的两个万能模板(借鉴试用多年,从未出错)

常见问题:为何mid有两种取值模板?

三:经典题目

 问题一:

 问题二(浮点数二分,但是比整数二分要简单):

 问题三:

四:隆重介绍头文件中的lower_bound和upper_bound函数

 五:拓展题型

山脉数组

 六:习题练习(吃透这些题型即可彻底掌握二分)

                                                                                                        


 一:二分查找概念定义

        二分查找解决的是单调函数上的查找问题。然后就有人问了,我遇到的二分查找都是在数组中找一个数,这个也是函数吗?
        广义地来说,数组就是一些离散的点,所以它是一种离散函数。所以,数组元素的查找其实也是在函数中进行查找。如下图所示,代表的是一个五个元素的数组:

6553f775b8e04ff58370b6ef41813437.png


 二:整数二分查找的两个万能模板(借鉴试用多年,从未出错)

二分模板一共有两个,分别适用于不同情况。
算法思路:假设目标值在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了目标值。

模板1(往左找答案):当我们将区间[l, r]划分成[l, mid][mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

模板2(往右找答案): 当我们将区间[l, r]划分成[l, mid - 1][mid, r]时,其更新操作是r = mid - 1或者l = mid;此时为了防止死循环,计算mid时需要加1

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

常见问题:为何mid有两种取值模板?

答:我们拿第二个模板举例子,如果在第二个模板的情况下取mid=l+r>>1,在l+1=r且check函数成立的情况下,那么会运行l=mid这条语句,但是因为mid=l+r>>1,因此此时的l=mid=l,等于没变,所以就会陷入死循环,因此第二种模板的mid要上取整,也就是mid=l+r+1>>1


三:经典题目

 问题一:

cee2a680b5b84530bf33dbdb5e77d9fb.png

#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int q[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);

    while (m -- )
    {
        int x;
        scanf("%d", &x);

        int l = 0, r = n - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (q[mid] >= x) r = mid;
            else l = mid + 1;
        }

        if (q[l] != x) cout << "-1 -1" << endl;
        else
        {
            cout << l << ' ';

            int l = 0, r = n - 1;
            while (l < r)
            {
                int mid = l + r + 1 >> 1;
                if (q[mid] <= x) l = mid;
                else r = mid - 1;
            }

            cout << l << endl;
        }
    }

    return 0;
}

注:该题目十分经典,用到了两个二分的模板,思路并不难,注意输出的格式就行


  问题二(浮点数二分,但是比整数二分要简单):

 5b0049746698497c85f87f0230a55128.png

 法一(浮点数二分):

#include <iostream>

using namespace std;

int main()
{
    double x;
    cin >> x;

    double l = -100, r = 100;
    while (r - l > 1e-8)
    {
        double mid = (l + r) / 2;
        if (mid * mid * mid >= x) r = mid;
        else l = mid;
    }

    printf("%.6lf\n", l);
    return 0;
}

法二(c++函数crbt):

#include<bits/stdc++.h>

using namespace std;

int main ( ) {

    double  n ;

    cin >>n ;

    printf ( "%lf" , cbrt ( n ) ) ; // cbrt 求三次方根

    return 0 ;

}

注:浮点数二分的模板很简单,但是要注意精度的问题,一般精度eps取到1e-8就肯定可以AC;法二就是利用库函数了,sqrt是求二次方根,crbt是求三次方根,两者都是默认保留六位小数


 问题三:

4b9955342ede409db47c0e52bea189eb.png

#include <iostream>

using namespace std;

const int N = 100010;

int n;
int q[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);

    int x;
    scanf("%d", &x);

    int l = 0, r = n - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (q[mid] >= x) r = mid;
        else l = mid + 1;
    }
     printf("%d",l);


    return 0;
}

注:返回其应该插入的位置,很明显是往左找答案,应该用模板1


四:隆重介绍头文件<algorithm>中的lower_bound和upper_bound函数

在从小到大的排序数组中,

lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
 

举例: 

#include <iostream>
#include<algorithm>

using namespace std;

const int N = 100010;

int n, m;
int q[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
  
    int x;
    scanf("%d", &x);
    
    int res1 = lower_bound(q,q+n,x)-q;//返回数组中第一个大于或等于被查数的值
    int res2 = upper_bound(q,q+n,x)-q;//返回数组中第一个大于被查数的值
    printf("%d  %d\n",res1,res2);
    
    return 0;
}

//输出1 3

 五:拓展题型

山脉数组

private int findMountainTop(MountainArray mountainArr, int left, int right) {
        while (left < right) {
            int mid = left + right>>1;
            if (mountainArr.get(mid) < mountainArr.get(mid + 1)) {
                // 下一轮搜索区间 [mid + 1..right]
                left = mid + 1;
            } else {
                // 下一轮搜索区间 [left..mid]
                right = mid;
            }
        }
        // left == right
        return left;
    }

 注:这道题目的本质就是三个二分,上述我只给出最具有思考意义的二分,其他两段二分和模板一模一样背过就行。这个二分的check函数具有一定的含金量

当mid<mid+1,说明答案在右边,需要执行left=mid+1的操作,否则right=mid


 六:习题练习(吃透这些题型即可彻底掌握二分)

序号题目链接难度系数
1二分查找★☆☆☆☆
2猜数字大小★☆☆☆☆
3两数之和-输入有序数组★★☆☆☆
4搜索插入位置★★☆☆☆
5查找插入位置★★☆☆☆
6寻找比目标字母大的最小字母★★☆☆☆
7两数之和★★☆☆☆
8和为s的两个数字★★☆☆☆
9排序数组中两个数组之和★★☆☆☆
10按权重生成随机数★★☆☆☆
11按权重随机选择★★☆☆☆
12区域内查询数字的频率★★★☆☆
13采购方案★★★☆☆
14早餐组合★★★☆☆
15寻找峰值★★★☆☆
16最大连续1的个数III★★★☆☆
17尽可能使字符串相等★★★☆☆
18制作m竖花所需要的最小天数★★★☆☆
19统计【优美子数组】★★★★☆
20摘水果★★★★☆
21山脉数组中查找目标值★★★★☆

                                                                                                        

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码kobe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值