新的角度看二分
- 二分就是将数组分为两段
- 因此,问题的最终目标是找出蓝红边界
朴素算法
- 红色指针一开始指向最右超出范围处,随后不断向左移动,直到找到蓝红边界;或者蓝色指针…
- 时间复杂度 O ( n ) O(n) O(n)
二分查找
- 红蓝指针的移动其实代表着红蓝区域的拓展。那么,有什么更快拓展的方式呢?由于数组本身有序…便可以快速拓展红色 或 蓝色区域
- 数组下标有效范围
[
0
,
n
−
1
]
[0,n - 1]
[0,n−1],因此,初始时
l = -1, r = n;
- 循环的结束条件是
l + 1 != r
,因为l指向蓝色边界最右边,r指向红色边界最左边,当l + 1 == r时整个数组已经由灰色未知被划分完毕 - 如果m是红色区域,直接
r = m
;同理… - 最后结果返回l还是r要看情况
细节1.为什么l的初始值为-1,r的初始值为n
- 假如最终整个数组都该是红色区域,如果l初始化为0,l一开始就处在红色区域内,这会造成错误;同理…
细节2.m是否始终处于[0,n)以内
- 只有处在这个区间以内,m才是有意义的
- 先看m的下界,由于
m = (l + r) / 2
(向下),因此如果我们想让m尽可能小,就要l和r尽可能小,l的最小值是初始值-1,r如果能进入循环体,最小值是1(不是0),因此m最小值为0 - 再看m上界,同理,r的最大值为n,l如果要进入循环体,最大值为n - 2,因此m最大值为n - 1
细节3.更新指针时,能不能写成l = m + 1或者r = m - 1
- 如果刚好m指向蓝色区域的最后一个位置,l如果变为m + 1,就错了;同理…
细节4.会不会陷入死循环
- 分别考虑
l + 1 == r
l + 2 == r
l + 3 == r
的情况 l + 1 == r
,退出循环l + 2 == r
,也就是它们之间隔一个元素,当进入循环体时,m就是它们之间隔着的那个元素,接下来,要么将l设置成m,要么将r设置成m,也就是说,在循环体结束时,l的下一个元素一定是r,会回归到第一种情况并退出循环体l + 3 == r
,会回归到第二种情况或者回归到第一种情况
例子 与 c++库函数
- lower_bound对应大于等于
- upper_bound对应大于
一般流程
- 后处理指的是 例如 只有蓝色区域/红色区域的返回值处理问题(需要特判)等
如果没有蓝色区域或者红色区域
- 那么l或者r会不在有效范围内
- 最后结果特判,l的初始值为-1,r的初始值为n
浮点数的二分
- 题目 :通过二分查找的方式计算 2 \sqrt{2} 2,要求误差小于 1 0 − 6 10^{-6} 10−6
- 退出循环的条件为
r - l <= 1e-8
- 要求误差小于 1 0 − 6 10^{-6} 10−6,相当于 结果保留6位小数;这样,循环退出的条件是 1 0 − 8 10^{-8} 10−8,而输出则是六位小数
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double l = -10000, r = 10000;
while (r - l > 1e-8)
{
double mid = (l + r) / 2;
if (mid * mid >= 2) r = mid;
else l = mid;
}
// printf("%.6lf\n", l);
cout << fixed << setprecision(6) << l << endl;
return 0;
}