二分查找
本文将使用画图来理解二分,并且介绍了经典的二分查找板子,例题。
能够使用二分查找的两个前提:
- 二分查找仅使用于数组,这样才能通过判断大小关系来排除一半的搜索区间;
- 要求输入数据是有序的,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。
整数二分
根据上面两个前提,很多人认为二分的本质是单调性,但实际上并不是的,二分的本质是边界。
假设给定一个区间,在这个区间上给定了某种性质,使得在右半区间满足这个性质,左半区间不满足这个性质,那么二分既能够查找左半区间的边界,也可以寻找右半区间的边界。
现在我们来看一下 左半区间二分查找:
再看一下右半区间二分查找:
那么这里有一个问题,
为什么两种二分,mid的写法有一个 + 1有一个并没有(对应向上取整和向下取整)
下面举一个例子:
看一下为什么要加上1
整数二分实战思想
如果你想让答案在右边区间,那么你得让mid满足左区间的性质,反之,可求左区间
整数二分实战模板
二分模板一共有两个,分别适用于不同情况。
版本1
其更新操作是r = mid,计算mid时不需要加1。
1 2 3 4 5 6 7 8
| int binarySearch(int l, int r){ while(l < r){ int mid = l + r >> 1; if(check(mid)) r = mid; else l = mid + 1; } return l; }
|
版本2
其更新操作是l = mid,此时为了防止死循环,计算mid时需要加1。
1 2 3 4 5 6 7
| int binarySearch(int l, int r){ while(l < r){ int mid = l + r + 1 >> 1; if(check(mid)) l = mid; else r = mid - 1; } }
|
复杂度分析
时间复杂度:O(logn)(折半查找树)
空间复杂度:O(1)(使用常数大小空间 l, r, mid)
当数组长度很大时,加法 (l + r) 的结果可能超出 int
类型的取值范围。可换为:
1 2 3
| int mid = l + r >> 1;
int mid = l + (r - l) >> 1;
|
整数二分经典例题
问题描述
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1
。
输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1
。
数据范围
1 ≤ n ≤ 100000
1 ≤ q ≤ 10000
1 ≤ k ≤ 10000
输入样例:
输出样例:
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <iostream> #include <cstring> #include <algorithm>
using namespace std;
const int N = 100010;
int n, Q; int q[N];
int main() { cin >> n >> Q; for (int i = 0; i < n; i ++ ) cin >> q[i]; while(Q --){ int x; cin >> 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) puts("-1 -1"); 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; }
|
浮点数二分经典例题
问题描述
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n 。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留 6 位小数。
数据范围
−10000 ≤ n ≤ 10000
输入样例:
输出样例:
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream> #include <cstring> #include <algorithm>
using namespace std;
int main() { double n; cin >> n; double l = -10000 , 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); }
|
技术支持
hello-algo
AcWing789.数的范围
AcWing 790. 数的三次方根