1. 概念
我们常用二分查找来查找某一个有序数组中的某个元素,但是二分查找同样可以用于查找边界。
二分的思想是在一个区间内每次通过缩小一半的空间来查找答案或者边界,然后向答案所在的区间继续下一步的处理,每一次的区间都会把答案覆盖掉,直到区间长度为一就代表找到所需要的答案或者边界。
后续情况默认区间有序
2. 本质
当给定一个区间,该区间符合某种性质,且该性质在右半边区间是满足的,在左半边区间是不满足的,且这两个区间并无交点。
二分的本质就是当可以找到这样一个性质,使得一半满足性质,一半不满足性质,那么二分就可以寻找这样的边界。
3. 步骤
- 确定边界。
- 套模板。
- 思考二分的是哪一个边界,性质是什么。
- 写
check函数判断。 - 思考
check函数结果应该处于哪一个边界范围内。
3. 代码模板
3.1. 整数二分模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
// check()判断mid是否满足性质
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
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;
}
3.2. 浮点数二分模板
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
// eps 表示精度,取决于题目对精度的要求,一般比题目要求精度高两位
const double eps = 1e-6;
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
4. 例题
4.1. 789. 数的范围 - AcWing题库
#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;
int 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";
else
{
cout << l << " ";
int l = 0;
int r = n - 1;
// 找有边界
while(l < r)
{
int mid = l + r + 1 >> 1;
if(q[mid] <= x) l = mid;
else r = mid - 1;
}
cout << l;
}
cout << endl;
}
return 0;
}
4.2. 790. 数的三次方根 - AcWing题库
#include <iostream>
using namespace std;
int main()
{
double n;
scanf("%lf", &n);
double l = -10000;
double r = 10000;
while(r - l > 1e-8)
{
double mid = (l + r) / 2;
if(mid * mid * mid >= n) r = mid;
else l = mid;
}
printf("%lf\n", l);
return 0;
}
5. 注意点
当给定一个二分问题,先写一个check函数,当符合check函数时该如何更新区间,不符合时该如何更新区间。