二分查找与二分答案
二分查找
- 对于一个有序序列,通过二分折半查找,
- 可以实现以 O(logn) 的复杂度找到目标值的位置或目标值的附属值 (在离散化操作中,查询映射后下标的操作)
二分答案
- 对于已经给定答案区间,要求 “最小的最大”or“最大的最小” 时,多用二分答案来快速查找答案。
复杂度分析
- 对于长度为n的序列,进行一次查找,由于每次查找区间长度会缩短一半,
- 因此一次成功的二分查找复杂度为O(logn)
- 对于m次二分操作,则复杂度会变为 O(m·logn) ,同时,根据题目的 check函数 ,代码运行常数会受到一定影响(即 check函数 越复杂常数越大,不保证出题人会恶意卡常)
题目可能的关键词
- 二分查找
- 谨记:二分查找是工具
- 对于题目中需要查找下标,多个序列求一个集合(i,j,k)的对数等,查找时从暴力角度思考会有遍历全程的操作,多用二分查找(不过一般也可以用map或者unordered_map来解决)。
- 二分答案
- 若答案数字范围在 0≤n≤1e6 题目情景有比较明确的单调性存在
- 对于求满足题意的最大值,至少可以满足题意思的最小值
二分模版
整数二分模版
-
写好check函数是完成二分查找的关键
bool check(int x) { } -
整数二分让人头疼的边界问题
- 由于c++,java中除法默认是向下取整,
- 因此,mid在确定时需要明确是否进行向上取整处理
- 这个时候背一下固定的模版就会很有用
模版 bserach_l
- 寻找第一个大于(等于)条件的最小值
int bserach_l(int l, int r, int x)
{
while (l < r) {
int mid = l + r >> 1;//不需要加,一因为求的是lmin
if (check(mid))
r = mid;
else
l = mid + 1;
}
return l;
}
模版 bserach_r
- 寻找第一个小于(等于)条件的最大值
int bserach_r(int l, int r, int x)
{
while (l < r) {
int mid = l + r + 1 >> 1;//注意+1,rmax要向上取
if (check(mid))
l = mid;
else
r = mid - 1;
}
return l;
}
PS:本人秉持实用性原则,理解记忆同时进行。
- 在更新 l 和 r 时
-
以下情况不用 +1
r = mid,l = mid+1 -
以下情况要 +1
l = mid,r = mid-1
- 边界问题的相关证明,可以写一写样例,大致的卡边界时,如果l = mid时,不加1,会导致在更新时一直是集合中有两个元素,从而导致死循环
模版题
浮点数二分模版
- 用的比较少 可以用来求根,简单来说就是二分迭代求根,收敛的速度不是很快 but,如果求根还是首推牛顿迭代,或者在用一下埃特金加速,在机器学习更常用
浮点数二分模版
bool check(double x) {} //同样的check函数
double bsearch_double(double l, double r)
{
const double eps = 1e-8;
//eps表示精度,一般比题目要求多2位就行了
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}