2022-11-03 Acwing每日一题
本系列所有题目均为Acwing课的内容,发表博客既是为了学习总结,加深自己的印象,同时也是为了以后回过头来看时,不会感叹虚度光阴罢了,因此如果出现错误,欢迎大家能够指出错误,我会认真改正的。同时也希望文章能够让你有所收获,与君共勉!
今天主要学习二分法查找,二分主要分为两种类型,一种是整数二分查找,一种是浮点数二分查找。先看看整数二分,其模板如下:
整数二分
//查找左边界 SearchLeft 简写SL
int SL(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
//查找右边界 SearchRight 简写SR
int SR(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1; //需要+1 防止死循环
if (check(mid)) l = mid;
else r = mid - 1;
}
return r;
}
最有效的方式就是通过题目来理解二分。
数的范围
给定一个按照升序排列的长度为 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
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
个人解析
引用自[https://www.acwing.com/solution/content/107848/]
我们可以理解为我们只需要找到这个数组成的区间左端点和右端点即可知道这段区间的位置,而查找左端点和右端点就可以使用二分来快速的查找(当然暴力也可以做)。
1.我们先考虑查找左端点时所需要的条件, 比较a[mid]
与x
,什么时候这个条件成立时能缩小右端点r = mid
,反之,条件不成立时,缩小左端点l = mid+1
我们可以想到,当a[mid]
比x
大时,需要往左边寻找,当a[mid]
比x
小时,需要往右边寻找,最终找到结果时一定有l==r
(因为循环条件l<r
)
2.再来考虑右端点的条件,参考查找左端点,我们需要尽可能地改变左端点l = mid
,因此结论为,当a[mid]
比x
要小时,要往右边寻找,当a[mid]
比x
要大时,要往左边寻找。
3.需要注意的是,模板中找左端点时mid = l + r >> 1
,而找右端点时mid = l+r+1 >> 1
,我们怎么来分辨什么时候+1,什么时候不+1呢,并且很多题目都不会明确告诉你要求区间端点。这时我们可以看条件成立时Check(a[mid],x)
的缩小区间这一步,如果为r=mid
就不需要+1,如果为l=mid
就需要+1。
综上,这道题算是完成了。
代码实现
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int q[100000];
int main(void){
int n,m;
cin >> n >> m;
for(int i=0; i < n ; ++i) cin >> q[i];
while(m--){
int l = 0,r = n-1,x;
cin >> x;
// 1.找到区间的左端,满足条件(x将区间分为两段,要找右端的最左边,满足x右端数的条件往左边缩小范围)
while( l<r ){
int mid = l + (r-l)/2;
if(q[mid] >= x){
r = mid;
}
else{
l = mid + 1;
}
}
if(q[l] != x){
cout << "-1" << " " << "-1" <<endl;
}
else{
cout << l << ' ';
l = 0 , r = n-1;
// 满足x左端数的条件往右边缩小,找最大值,注意往右边缩小范围时要+1防止向下取整
while(l<r){
int mid = (l+r+1)/2;
if(q[mid] <= x){
l = mid;
}
else{
r = mid-1;
}
}
cout << r << endl;
}
}
return 0;
}
力扣上跟这道题很像的二分:点击这里
浮点数二分
这就比较简单了,不需要考虑找边界,就是找一个浮点数呗,因此退出循环的条件也变成了r-l
小于很小的数时才会退出。
模板为:
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
这道浮点数二分的题就是模板:数的三次根。