第一章 基础算法 二分

1、算法思路

二分算法不仅仅只适用于具有单调性的数组上, 其本质是
对于一个数组,左边(右边)符合某条件A,右边(左边)符合某条件B,并且没有元素可以同时满足条件A和条件B,我们都可以通过二分,找到符合条件A的或者B的分界点

二分又可以分成整数二分和浮点数二。
对于整数二分,我们可以找到两个分界点,如下图。为了寻找这两个分界点,我们在处理的时候会有不同。
在这里插入图片描述
而对于浮点数二分,只需要在足够小的区间内任意选取一个点作为分界点。
在这里插入图片描述

整数二分具体步骤

  1. 定义check()函数,用来判断某元素是否符合某条件

  2. 判断符合该条件的元素位于整个区间的哪一边

  3. 具体处理,假设符合该条件的分界点位置为x,l为当前分析区间的左端点,r为右端点

    3.1 如果是位于整个数组的左边

    1. 取中间位置mid = (l + r +1) / 2 (整数除法)
    2. 如果mid元素符合check函数,则有l <= mid <= x <= r,将l设为mid,则新的l,r区间中,仍然包含着x。
    3. 如果mid元素不符合check函数,则有l <= x < mid < r,将r设为mid - 1(因为mid不符合check函数而x符合check函数,mid必不能等于x),则新区间l,r中,仍包含着x。
    4. 当l == r时,则找到了x

    3.2 如果是位于整个数组的右边

    1. 取中间位置mid = (l + r) / 2 (整数除法)
    2. 如果mid元素符合check函数,则有l <= x <= mid <= r,将r设为mid,则新的l,r区间中,仍然包含着x。
    3. 如果mid元素不符合check函数,则有l <= mid < x < r,将l设为mid + 1(因为mid不符合check函数而x符合check函数,mid必不能等于x),则新区间l,r中,仍包含着x。
    4. 当l == r时,则找到了x

可以看到,不断缩小的[l,r]区间中始终包含着x,所以当l==r的时候,就找到了x

浮点数二分具体步骤

  1. 定义check()函数,用来判断某元素是否符合某条件

  2. 判断符合该条件的元素位于整个区间的哪一边

  3. 取中间位置 mid = (l + r) / 2 (浮点数除法)

    3.1 如果是位于整个数组的左边

    1. 如果mid元素符合check函数,将l设为mid,则新的l,r区间中,仍然包含着x。
    2. 如果mid元素不符合check函数,将r设为mid ,则新区间l,r中,仍包含着x。

    3.2 如果是位于整个数组的右边

    1. 如果mid元素符合check函数,将r设为mid,则新的l,r区间中,仍然包含着x。
    2. 如果mid元素不符合check函数,将l设为mid ,则新区间l,r中,仍包含着x。
  4. 当 r - l 足够小时时,则区间内任何一个点都可以当做结果

可以看到,不断缩小的[l,r]区间中始终包含着x,所以当r - l足够小的时候,区间内任何一个数都可以算作结果

2、模板代码

以下代码模板参考Acwing

bool check(int t);
//整数二分
//符合条件的元素位于区间右边
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
//符合条件的元素位于区间左边
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;
}
//浮点数二分
//符合条件的元素位于区间右边
double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}
//符合条件的元素位于区间左边
double bsearch_4(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) l = mid;
        else r = mid;
    }
    return l;
}

1、代码解释

我认为二分就是保证目标位置x一直在区间中的情况下,不断地缩小区间,从而找到这个位置。
注意浮点二分中,最后保留的区间精度取决于题目要求保留的答案位数,需要多保留两位。

2、具体示例

对于一个数组,我们寻找大于3的第一个数字的位置

1 2 3 3 4 4 5

我们看到我们想寻找的位置是在整个数组的右边

元素值1233445
元素位置0123456
查找区间lr
mid = (l + r) / 2 = 3

第三个位置值为3 不符合大于3的要求,l = mid + 1

元素值1233445
元素位置0123456
查找区间lr
mid = (l + r) / 2 = 4

第四个位置的值为4 符合大于3的要求,r = mid

元素值1233445
元素位置0123456
查找区间l r

此时l == r可以返回位置 4

3、边界问题分析

1. 避免递归时区间无限划分问题

两个mid的取法不一样,一个是上取整,一个是下取整,是因为需要避免无限划分的问题。
比如符合该条件的元素位于整个区间右边的情况下,符合条件的时候,r = mid , 否则l = mid + 1。
如果是上取整,也就是mid = (l + r + 1 ) / 2。当 l和r相差1的时候,也就是r比l大1的时候,mid = r了,此时如果符合条件的时候,r不会变,区间仍然是l,r,会一直循环下去。
如果是下取整,上述情况时,mid = l,区间会变成(l,l)从而结束循环。

3、 算法复杂度分析

1.时间复杂度分析

每次循环都会把区间缩小一半,将区间缩小到1需要缩小 l o g 2 N log_2N log2N次,所以时间复杂度为 O ( l o g 2 N ) O(log_2N) O(log2N)

参考资料

  1. Acwing
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值