二分查找
整数二分
模板
- 99 % 99\% 99%的整数二分都可以用以下两个模板求得答案
- c h e c k ( ) check() check()函数代表一个分界点,根据分界点,我们可以把序列分为两部分
模板一
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
模板二
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
例题与讲解
根据模板,来解决一道实际问题
现有非严格递增序列 a = { 5 , 7 , 7 , 7 , 8 , 10 } a = \{5,7,7,7,8,10\} a={5,7,7,7,8,10},希望在给定一个目标值 x x x,返回序列中 x x x的第一个和最后一个位置
假设 x = 7 x = 7 x=7
首先,我们看一下如何找 x x x在序列中的第一个位置;我们根据序列的性质,将序列分为两部分
|-------------
5, 7, 7, 7, 8, 10
l mid r
-
可以观察到,序列的左边的值,为 < x \lt x <x的部分;序列右边,为 ≥ x \ge x ≥x的部分
-
因此,我们可以将 c h e c k ( m i d ) → a [ m i d ] > = x check(mid) \rightarrow a[mid] >= x check(mid)→a[mid]>=x
-
这个分法的分界点即为 x x x出现的第一个位置
-
从一开始范围的变化进行思考,假设 m i d mid mid在最开始的位置如上
- 可以发现, a [ m i d ] a[mid] a[mid]满足 c h e c k ( ) check() check()
- 此时,我们要逼近分界点,则需要令 r = m i d r = mid r=mid
-
因此发现,根据模板,在求 x x x第一次出现的位置时,我们可以用模板一
阶段代码如下:
int l = 0, r = a.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (a[mid] >= x) r = mid;
else l = mid - 1;
}
接着,我们看一下如何找 x x x在序列中的最后一个位置
---------|
5, 7, 7, 7, 8, 10
l mid r
- 我们可以发现,令 c h e c k ( m i d ) → a [ m i d ] < = x check(mid) \rightarrow a[mid] <= x check(mid)→a[mid]<=x,可以将序列分为两部分
- 同样,从一开始范围的变化进行思考,假设
m
i
d
mid
mid在最开始的位置如上
- 此时 a [ m i d ] a[mid] a[mid]满足 c h e c k ( ) check() check()函数,要逼近分界点,则需要令 l = m i d l = mid l=mid
- 因此发现,根据模板,在求 x x x最一后次出现的位置时,我们可以用模板二
阶段代码如下:
int l = 0, r = a.size() - 1;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (a[mid] <= x) l = mid;
else l = mid - 1;
}
总结与完善代码
整数二分的流程大致如下:
- 确定 c h e c k ( ) check() check()函数,即找到一个分界点将序列分为两部分
- 思考当满足 c h e c k ( ) check() check()函数时,第一步该怎么走
- 第一步的走法确定之后,即可找到对应的模板,直接写就行了
例题链接:34. 在排序数组中查找元素的第一个和最后一个位置
完整代码
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int x) {
if (nums.size() < 1) {
return {-1, -1};
}
// 查找x出现的第一个位置
vector<int> res;
int l = 0, r = nums.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (nums[mid] >= x) r = mid;
else l = mid + 1;
}
// 未找到,代表序列中不存在x
if (nums[l] != x)
{
return {-1, -1};
}
res.push_back(l);
// 查找x出现的最后一个位置
// 这里不更新l的原因是因为:
// 当前l在x出现的第一个位置上,x最后出现的位置一定是在现在的l和更新后的r内部
r = nums.size() - 1;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (nums[mid] <= x) l = mid;
else r = mid - 1;
}
res.push_back(l);
return res;
}
};
浮点数二分
即 l , r l,r l,r为 d o u b l e double double或 f l o a t float float类型,直接直觉上的判断处理即可,可根据代码理解
退出循环的条件为 ∣ r − l ∣ ≤ ξ |r - l| \le \xi ∣r−l∣≤ξ
典型例题:求数的三次方根
− 10000 ≤ x ≤ 10000 -10000 \le x \le 10000 −10000≤x≤10000
#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;
}