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

目录

 一:二分查找概念定义

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

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

三:经典题目

 问题一:

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

 问题三:

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

 五:拓展题型

山脉数组

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

                                                                                                        


 一:二分查找概念定义

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

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


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

二分模板一共有两个,分别适用于不同情况。
算法思路:假设目标值在闭区间[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


三:经典题目

 问题一:

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

#include 

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;
}

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


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

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

 法一(浮点数二分):

#include 

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

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 

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


四:隆重介绍头文件中的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 
#include

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


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

序号 题目链接 难度系数
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 山脉数组中查找目标值 ★★★★☆

                                                                                                        

你可能感兴趣的:(算法,c++,算法,java,leetcode,蓝桥杯)