一、二分算法简介

当我们要从一个序列中查找一个元素的时候,最简单无脑的方法就是顺序查找法,但由于在大数据情况下爆炸的时间复杂度而舍弃。

最常见的方法是二分查找,也称折半查找(Binary Search),它是一种效率较高的查找方法。

最近偶然看到 『LeetCode』 讨论中的大佬总结的 二分查找从入门到入睡 ,虽然文章巨长,但总结的很全,一些边界问题讲的也很细,其中包括了Y总的二分思路,非常推荐看一看!!


二、 算法基本思想和流程(时间复杂度 O ( l o g n ) O(logn) O(logn) )

  • 算法思想: 假设在闭区间 [l, r] 中寻找目标值 x ,二分的思想是每次将区间长度缩小一半,当l = r时,我们就找到了目标值。
  • 注意:
    • 二分的本质并不是单调性,二者并没有必要的关系。 因为数据有单调性一定可以二分,但可以二分的题目不一定非要有单调性。
    • 二分的本质在于:在区间 [l, r] 中有性质,使得区间可以一分为二,一半边区间满足该性质而另一半区间不满足性质, 这样的话二分算法可以寻找这个性质的边界(红色和绿色的边界都行,因为是整数二分所以两边界不重合)
  • 整数二分: 文章中的两个模板分别对应着二分红色边界和二分绿色边界。
    • 【模板一】二分红色边界

      • Step1——确定中间点mid = (l + r + 1) / 2
      • Step2——判断
        • mid 满足性质 check(mid) ,即 if(check(mid)) = True,则更新区间为 [mid, r],即令l = mid 即可;
        • mid 不满足性质 check(mid) ,即 if(check(mid)) = False,则更新区间为 [l, mid - 1],即令r = mid - 1 即可;
      • Step3——循环前两步,不断缩短空间,直到 l >= r,边界值为 l
    • 【模板二】二分绿色边界

      • Step1——确定中间点mid = (l + r) / 2 (上取整是为了防止死循环);
      • Step2——判断
        • mid 满足性质 check(mid) ,即 if(check(mid)) = True,则更新区间为 [l, mid],即令r = mid 即可;
        • mid 不满足性质 check(mid) ,即 if(check(mid)) = False,则更新区间为 [mid + 1, r],即令l = mid + 1 即可;
      • Step3——循环前两步,不断缩短空间,直到 l >= r,边界值为 l
  • 浮点数二分 :相对于整数二分更简单,无需考虑边界问题,理解了整数二分后,浮点数二分不成问题。

三、 整数二分模板(背诵)

【模板一】

  • 循环条件:l < r
  • 划分区间:[l, r][l, mid - 1][mid, r]
  • 更新操作:r = mid - 1或者l = mid
  • 注意:计算mid时为了避免死循环需要加1,即mid = l + r + 1 >> 1
bool check(int x) {/* ... */} // 检查x是否满足某种性质int bsearch(int l, int r)
{while (l < r){int mid = l + r + 1 >> 1;if (check(mid)) l = mid;else r = mid - 1;}return l;
}

【模板二】

  • 循环条件:l < r
  • 划分区间:[l, r][l, mid][mid + 1, r]
  • 更新操作:r = mid或者l = mid + 1
  • 注意:计算mid时不需要加1,即mid = l + r >> 1
bool check(int x) {/* ... */} // 检查x是否满足某种性质int bsearch(int l, int r)
{while (l < r){int mid = l + r >> 1;if (check(mid)) r = mid;else l = mid + 1;}return l;
}

3. 浮点数二分模板(背诵)

bool check(double x) {/* ... */} // 检查x是否满足某种性质double bsearch(double l, double r)
{const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求while (r - l > eps)  // 两种写法:此时是用精度控制循环次数,直接控制循环100次也是OK的!{double mid = (l + r) / 2;if (check(mid)) r = mid;else l = mid;}return l;
}

四、 使用模板的几个关键问题

① 如何选择用哪个模板?

做题的顺序首先是确定 check() 函数,再进行区间划分的分析,再确定使用哪个模板。

可以看到:当 l = mid 时,我们使用模板一,且 mid = (l + r) / 2 为下取整;当 r = mid 时,我们使用模板二,且 mid = (l + r + 1) / 2 需要上取整;

② 为什么模板一需要加上 ‘1’ ,即 ‘mid = (l + r + 1) / 2’,而模板二又不需要了 ?

  • 使用模板一时,若 l = r - 1,即 lr 只差 1 的时候,如果 mid = (l + r) / 2 下取整的话,结果是等于 l 的,则一旦 if(check(mid)) = True ,更新区间会一直陷入到 [mid, r] = [l, r] 死循环中。
  • 同理,使用模板二时,当 l = r - 1,即 lr 只差 1 的时候,如果 mid = (l + r + 1) / 2 下上取整的话,结果是等于 r 的,则一旦 if(check(mid)) = True ,更新区间会一直陷入到 [l, mid] = [l, r] 死循环中。

五、 应用:模板题

【整数二分 - 模板题】AcWing 789. 数的范围
【思路】想要找到目标值 x 的起始坐标,可理解成找到 ≥x 的最小值,再判断找到的边界值是否与 x 相等,若不相等返回 -1;同样,想要找到目标值 x 的终止坐标,可理解成找到 ≤x 的最大值,再判断找到的边界值是否与 x 相等,若不相等返回 -1

【C++代码】

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

【浮点数二分 - 模板题】AcWing 790. 数的三次方根

【C++代码】

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

2022年06月26日:补充二分“相等返回”的模板

【模板三】相等返回

  • 循环条件:l <= r
  • 划分区间:[l, r][l, mid - 1][mid + 1, r]
  • 更新操作:r = mid - 1或者l = mid + 1
  • 注意:计算mid时不需要加1,即mid = l + r >> 1
int bsearch(int l, int r)
{while (l <= r){int mid = l + r >> 1;if (q[mid] == target) return mid;else if (q[mid] < target) l = mid + 1;else r = mid - 1;}return -1;
}

2022年06月30日:补充二分三个模板的理解

此处借鉴学习一下『 LeetCode 大佬 - yukiyama』 的总结表格,详解请参考:二分查找从入门到入睡

  • Y总的两个二分模板使用的循环条件是 l < r,结束循环条件必定是相等 l = r 终止,且二分范围一般来讲是 [0, n - 1]n 为数组长度。本质上在二分的过程中,mid 并没有完全覆盖整个数组,这怎么理解呢?

    • 我们先看模板一,我们考虑一种情况,当数组中所有元素都不满足性质时,这时候 r 值会一直缩小直到 l = r = 0 ,而在这个过程中 mid 不会取到 0 值就返回了,也就是说返回的 l = 0 值并没有通过性质的判断,无法确定是否满足我们所设的二分性质,因为这个性质的边界是可能存在于小于 0 上的。因此,保险起见,需要对输出的 l 再进行一次判断即可。总结来说,Y总的模板一是左开右闭的。
    • 同理我们分析模板二,同样考虑一种情况,当数组中所有元素都不满足性质时,这时候 l 值会一直扩大直到 l = r = n - 1 ,而在这个过程中 mid 不会取到 n - 1 值就返回了,也就是说返回的 l = n - 1 值并没有通过性质的判断,无法确定是否满足我们所设的二分性质,因为这个性质的边界是可能存在于大于 n - 1 上的。因此,保险起见,需要对输出的 l 再进行一次判断即可。总结来说,Y总的模板二是左闭右开的。
    • 所以,用Y总的模板一和模板二的时候需要小心一下 l 是否落到范围边界上了,此时就需要再次判定一下。或者将范围扩大到 [-1, n - 1] (模板一)/ [0, n] (模板二)。
  • 我们用同样的思想去看一看模板三(相等返回),不同于前两个模板,模板三的退出循环条件必定是相错终止,即 l - r = 1 ,按上述方法去分析的话,我们会发现模板三的 mid 会覆盖整个数组元素,因此模板三是左闭右闭的,且由于更新操作为r = mid - 1或者l = mid + 1,因此计算mid时不需要加1mid = l + r >> 1 即可。

二分算法详解:整数二分及浮点数二分算法(Binary Search)(含算法模板)相关推荐

  1. KMP算法详解:使用部分匹配表PMT来理解KMP算法,使用Java实现

    有些算法,适合从它产生的动机,如何设计与解决问题这样正向地去介绍.但KMP算法真的不适合这样去学.最好的办法是先搞清楚它所用的数据结构是什么,再搞清楚怎么用,最后为什么的问题就会有恍然大悟的感觉.我试 ...

  2. 【算法详解】数据结构:7种哈希散列算法,你知道几个?

    一.前言 哈希表的历史 哈希散列的想法在不同的地方独立出现.1953 年 1 月,汉斯·彼得·卢恩 ( Hans Peter Luhn ) 编写了一份IBM内部备忘录,其中使用了散列和链接.开放寻址后 ...

  3. C++中的STL算法详解

    1.STL算法详解 STL提供能在各种容器中通用的算法(大约有70种),如插入.删除.查找.排序等.算法就是函数模板,算法通过迭代器来操纵容器中的元素.许多算法操作的是容器上的一个区间(也可以是整个容 ...

  4. 离线强化学习(Offline RL)系列3: (算法篇) IQL(Implicit Q-learning)算法详解与实现

    [更新记录] 论文信息:Ilya Kostrikov, Ashvin Nair, Sergey Levine: "Offline Reinforcement Learning with Im ...

  5. 离线强化学习(Offline RL)系列3: (算法篇) AWAC算法详解与实现

    [更新记录] 论文信息:AWAC: Accelerating Online Reinforcement Learning with Offline Datasets [Code] 本文由UC Berk ...

  6. SF图像滤镜/美颜/美妆算法详解与实战

    本专栏将结合本热多年相关经验,从传统算法到火热的AI算法,给大家详细讲解目前在PC图像软件.手机图像处理类应用app,以及视频直播等应用类型中,图像视频的滤镜特效,人像美颜美妆特效的算法理论,并结合具 ...

  7. 离线强化学习(Offline RL)系列3: (算法篇) Onestep 算法详解与实现

    [更新记录] 论文信息: David Brandfonbrener, William F. Whitney, Rajesh Ranganath, Joan Bruna: "Offline R ...

  8. 多目标跟踪(MOT)中的卡尔曼滤波(Kalman filter)和匈牙利(Hungarian)算法详解

    多目标跟踪(MOT)中的卡尔曼滤波(Kalman filter)和匈牙利(Hungarian)算法详解 1. 概览 在开始具体讨论卡尔曼滤波和匈牙利算法之前,首先我们来看一下基于检测的目标跟踪算法的大 ...

  9. 十大经典排序算法详解(三)-堆排序,计数排序,桶排序,基数排序

    养成习惯,先赞后看!!! 你的点赞与关注真的对我非常有帮助.如果可以的话,动动手指,一键三连吧!!! 十大经典排序算法-堆排序,计数排序,桶排序,基数排序 前言 这是十大经典排序算法详解的最后一篇了. ...

  10. KMP算法详解及代码

    KMP算法详解及代码 KMP算法详解及代码 定义及应用 理论 基本概念 next 数组 总结 注意 代码 KMP算法详解及代码 最近正好在看字符串相关的算法内容,就顺便把KMP算法回顾了一下.相应的代 ...

最新文章

  1. .NET Winform也能画出类似QQ、飞信这样的窗口风格和控件效果
  2. 计算机科学与技术考,计算机科学与技术考研
  3. Python爬虫的终极必杀绝技
  4. css3毛玻璃模糊效果
  5. HTTP协议 TCP协议简要
  6. 如何用UE(UltraEdit)删除重复行?--转
  7. 分布式文件系统(HDFS)与 linux系统文件系统 对比
  8. 全局eslint不生效的处理
  9. LeetCode 837. 新21点(动态规划)
  10. 外卖餐饮点餐系统,连锁餐饮,公众号小程序源码2.1.5
  11. 天书夜读:从汇编语言到Windows内核编程
  12. linux端口快速释放,Linux 快速释放端口与释放内存缓存,linux释放端口缓存
  13. 人脸识别库-于仕琪老师库地址
  14. 我的理想200字计算机工程师,我的理想工程师作文(我的理想是做一名工程师)...
  15. 一文带你搞懂Python中的文件操作
  16. linux一些不要想当然的事(一)之目录权限
  17. 迅雷9屏蔽所有游览器和网站相应
  18. 【英语学习】英语语法术语表 English Grammar Terminology
  19. 第一章:HBase定义
  20. springboot启动源码分析3-环境配置

热门文章

  1. 设置文件编译规则的makefile---配置编译器环境的c_pp_properties.json---设置的文本配置seting.json
  2. 流量变现平台市场分析报告-
  3. 永久关闭Linux防火墙
  4. collections.defaultdict
  5. 驼峰命名法(CamelCase)和下划线命名法(UnderScoreCase)
  6. mac如何查看是否安装了git?
  7. Snort - manual 笔记(二)
  8. 正确简单地安装Tensorflow和Keras
  9. win10/win11安装时提示:“我们无法创建新的分区,也找不到现有分区”的解决方法
  10. python列表对应元素相乘_在python中,将两个列表中的每个元素相乘