二分算法详解:整数二分及浮点数二分算法(Binary Search)(含算法模板)
一、二分算法简介
当我们要从一个序列中查找一个元素的时候,最简单无脑的方法就是顺序查找法,但由于在大数据情况下爆炸的时间复杂度而舍弃。
最常见的方法是二分查找,也称折半查找(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——确定中间点
- 【模板二】二分绿色边界
- 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
。
- Step1——确定中间点
- 【模板一】二分红色边界
- 浮点数二分 :相对于整数二分更简单,无需考虑边界问题,理解了整数二分后,浮点数二分不成问题。
三、 整数二分模板(背诵)
【模板一】
- 循环条件:
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
,即l
和r
只差1
的时候,如果mid = (l + r) / 2
下取整的话,结果是等于l
的,则一旦if(check(mid)) = True
,更新区间会一直陷入到[mid, r] = [l, r]
死循环中。 - 同理,使用模板二时,当
l = r - 1
,即l
和r
只差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
时不需要加1
,mid = l + r >> 1
即可。
二分算法详解:整数二分及浮点数二分算法(Binary Search)(含算法模板)相关推荐
- KMP算法详解:使用部分匹配表PMT来理解KMP算法,使用Java实现
有些算法,适合从它产生的动机,如何设计与解决问题这样正向地去介绍.但KMP算法真的不适合这样去学.最好的办法是先搞清楚它所用的数据结构是什么,再搞清楚怎么用,最后为什么的问题就会有恍然大悟的感觉.我试 ...
- 【算法详解】数据结构:7种哈希散列算法,你知道几个?
一.前言 哈希表的历史 哈希散列的想法在不同的地方独立出现.1953 年 1 月,汉斯·彼得·卢恩 ( Hans Peter Luhn ) 编写了一份IBM内部备忘录,其中使用了散列和链接.开放寻址后 ...
- C++中的STL算法详解
1.STL算法详解 STL提供能在各种容器中通用的算法(大约有70种),如插入.删除.查找.排序等.算法就是函数模板,算法通过迭代器来操纵容器中的元素.许多算法操作的是容器上的一个区间(也可以是整个容 ...
- 离线强化学习(Offline RL)系列3: (算法篇) IQL(Implicit Q-learning)算法详解与实现
[更新记录] 论文信息:Ilya Kostrikov, Ashvin Nair, Sergey Levine: "Offline Reinforcement Learning with Im ...
- 离线强化学习(Offline RL)系列3: (算法篇) AWAC算法详解与实现
[更新记录] 论文信息:AWAC: Accelerating Online Reinforcement Learning with Offline Datasets [Code] 本文由UC Berk ...
- SF图像滤镜/美颜/美妆算法详解与实战
本专栏将结合本热多年相关经验,从传统算法到火热的AI算法,给大家详细讲解目前在PC图像软件.手机图像处理类应用app,以及视频直播等应用类型中,图像视频的滤镜特效,人像美颜美妆特效的算法理论,并结合具 ...
- 离线强化学习(Offline RL)系列3: (算法篇) Onestep 算法详解与实现
[更新记录] 论文信息: David Brandfonbrener, William F. Whitney, Rajesh Ranganath, Joan Bruna: "Offline R ...
- 多目标跟踪(MOT)中的卡尔曼滤波(Kalman filter)和匈牙利(Hungarian)算法详解
多目标跟踪(MOT)中的卡尔曼滤波(Kalman filter)和匈牙利(Hungarian)算法详解 1. 概览 在开始具体讨论卡尔曼滤波和匈牙利算法之前,首先我们来看一下基于检测的目标跟踪算法的大 ...
- 十大经典排序算法详解(三)-堆排序,计数排序,桶排序,基数排序
养成习惯,先赞后看!!! 你的点赞与关注真的对我非常有帮助.如果可以的话,动动手指,一键三连吧!!! 十大经典排序算法-堆排序,计数排序,桶排序,基数排序 前言 这是十大经典排序算法详解的最后一篇了. ...
- KMP算法详解及代码
KMP算法详解及代码 KMP算法详解及代码 定义及应用 理论 基本概念 next 数组 总结 注意 代码 KMP算法详解及代码 最近正好在看字符串相关的算法内容,就顺便把KMP算法回顾了一下.相应的代 ...
最新文章
- .NET Winform也能画出类似QQ、飞信这样的窗口风格和控件效果
- 计算机科学与技术考,计算机科学与技术考研
- Python爬虫的终极必杀绝技
- css3毛玻璃模糊效果
- HTTP协议 TCP协议简要
- 如何用UE(UltraEdit)删除重复行?--转
- 分布式文件系统(HDFS)与 linux系统文件系统 对比
- 全局eslint不生效的处理
- LeetCode 837. 新21点(动态规划)
- 外卖餐饮点餐系统,连锁餐饮,公众号小程序源码2.1.5
- 天书夜读:从汇编语言到Windows内核编程
- linux端口快速释放,Linux 快速释放端口与释放内存缓存,linux释放端口缓存
- 人脸识别库-于仕琪老师库地址
- 我的理想200字计算机工程师,我的理想工程师作文(我的理想是做一名工程师)...
- 一文带你搞懂Python中的文件操作
- linux一些不要想当然的事(一)之目录权限
- 迅雷9屏蔽所有游览器和网站相应
- 【英语学习】英语语法术语表 English Grammar Terminology
- 第一章:HBase定义
- springboot启动源码分析3-环境配置
热门文章
- 设置文件编译规则的makefile---配置编译器环境的c_pp_properties.json---设置的文本配置seting.json
- 流量变现平台市场分析报告-
- 永久关闭Linux防火墙
- collections.defaultdict
- 驼峰命名法(CamelCase)和下划线命名法(UnderScoreCase)
- mac如何查看是否安装了git?
- Snort - manual 笔记(二)
- 正确简单地安装Tensorflow和Keras
- win10/win11安装时提示:“我们无法创建新的分区,也找不到现有分区”的解决方法
- python列表对应元素相乘_在python中,将两个列表中的每个元素相乘