对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目只有题目分析,代码实现,代码误区
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -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/problem/content/791/
q & mid==q。所以严格说是三分,但是每次我们只舍弃了一半区间,也可以说是二分
=q
- arr[mid]>=q时,左端点在mid左侧,舍弃mid右侧,r = mid
- arr[mid]
- 注意此时的边界arr[mid]归属:
arr[mid]>=q,mid可能是边界,下一次继续查找mid,所以r=mid
arr[mid]- l == r时,arr[l] == q 则说明序列中有q,左边界就是l;反之arr[l] != q,则说明序列中无q,查找到的是序列中最近似q的数,输出-1
找右端点:mid满足右端点性质<=q,则舍弃mid左,l = mif;
mid不满足右端点性质>q,则舍弃mid右, r = mid - 1;
bool check(mid){
return arr[mid] >= q;
}
while(l < r){
int mid = (l+r) >> 1;
//check()函数就是判断mid是否满足左边界性质:>=q
if (check(mid)) r = mid;
else l = mid+1;
}
bool check(mid){
return arr[mid] <= q;
}
while(l < r){
int mid = (l+r+1)>>1;
//check()函数就是判断mid是否满足右边界性质:<=q
if (check(mid)) l = mid;
else r = mid-1;
}
#include
using namespace std;
const int N = 100010;
int n, q;
int arr[N];
int bsearch_left(int x){
int l = 0, r = n-1;
while(l < r){
int mid = (l+r)>>1;
if(arr[mid] >= x) r = mid;
else l = mid+1;
}
if (arr[l] == x) return l;
else return -1;
}
int bsearch_right(int x){
int l = 0, r = n-1;
while(l < r){
int mid = (l+r+1) >> 1;
if(arr[mid] <= x) l = mid;
else r = mid-1;
}
if(arr[l] == x) return l;
else return -1;
}
int main(){
cin >>n >>q;
for(int i=0; i<n; i++) cin>>arr[i];
for(int i=0; i<q; i++){
int x = 0;
cin >>x;
cout<<bsearch_left(x)<<" "<<bsearch_right(x)<<endl;
}
return 0;
}
为什么寻找左边界mid = (l+r)>>1;
但是寻找右边界mid = (l+r+1)>>1?
当l 和 r 距离很近,距离为1时:
l = r-1
如果将要替换l 的 mid = (l+r)>>1 由于向下取整,等于l,相当于区间没变,还是[l,r],陷入死循环
如果将要替换r 的 mid = (l+r)>>1 由于向下取整,等于1,此时区间变化,是[l,l],即找到了目标点
总结:由于>>1 或者 /2 的地板除法,导致r和l相距为1时,mid 总是等于l,此时对原本要替换l的情况(找右边界)无效,陷入死循环
寻找左边界
满足条件,替换r,不必防止地板除,mid = (l+r)>>1;
寻找右边界
满足条件,替换l,防止地板除,令mid = (l+r+1)>>1;
寻找左边界
mid满足条件:r = mid [l , mid]
mid可能是边界,可能是边界右,所以下一次查找要查mid
mid不满足条件:l = mid+1 [mid+1 , r]]
mid不可能是边界,下一次查找不用查mid了
寻找右边界
mid满足条件:l = mid [mid , r]
mid 可能是边界,下一次查找要查mid
mid不满足条件:r = mid-1 [l , mid-1
mid 不可能是边界,下一次查找不用查mid