今天是端午节,祝大家端午节快乐~
竟然这样,还不点点赞。
言归正传┏ (゜ω゜)=☞
目录
引入
二分查找算法思想
时间复杂度O(logN)
二分查找算法描述
二分查找算法的框架如下:
例题1:
例题2:查找m个数(tops主题库2646)
题目描述
输入格式
输出格式
样例
输入#1
输出#1
数据范围与提示
找朋友
题目描述
输入格式
输出格式
样例
输入数据#1
输出数据#1
说明/提示
和为给定数(summator)
题目描述
输入格式
输出格式
样例
输入#1
输出#1
数据范围
二分查找左边界
二分查找右边界
二分函数和答案
binary_search():二分查找函数
lower_bound():二分査找左边界
upper_bound:二分查找右边界
二分答案
例题1:
例题2:
二分查找也称折半查找,它是一种效率极高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列,就是:数据要是有序排列的
引入
现在有一堆硬币中混入了一枚假硬币,从外观上不能区分真假硬币,但是由于真假硬币的材质不一样,所以假硬币要轻一点点,用手无法直接区分。现在给你一个天平,请问要怎样找出假硬币?
思路:分两种情况讨论,第一种情况,奇数个的硬币:我们首先拿出一个硬币,把剩余的硬币进行平分,放在天平的两端,如果天平此时平衡,那我们手中拿的那枚硬币就是假硬币。如果天平倾斜,假的硬币比真的硬币轻一点点,所以说天平会往轻的那边倾斜,假的硬币就在轻的一边。我们接着把有假硬币的这些硬币,再进行平分,放在天平上称就可以了。
第二种情况:偶数个硬币,直接平分就可以。
二分查找算法思想
对于n个有序且没有重复的元素(假设为升序),从中查找特定的某个元素x,我们可以将有序序列分成规模大致相等的两部分,然后取中间元素与要查找的元素x进行比较,如果x等于中间元素,则查找成功,算法终止;如果x小于中间元素,则在序列的前半部分继续查找,否则在序列的后半部分继续查找。这样就可以将查找的范围缩小一半,然后在剩余的一半中继续重复上面的方法进行查找。
这种每次都从中间元素开始比较,并且一次比较后就能把查找范围缩小一半的方法,叫作二分查找。二分查找的时间复杂度是 O(logN),是一种效率较高的查找算法。
时间复杂度O(logN)
二分査找时间复杂度:log2(n)
推导:
因为二分查找每次排除掉…半的不适合值,所以对于n个元素的情况:
一次二分剩下:n/2
两次二分剩下:n/2/2 = n/4
m次二分剩下:n/(2^m)
在最坏情况下是在排除到只剩下最后一个值之后得到结果,即
n/(2^m)=l
所以由上式可得:2^m=n
进而可求出时间复杂度为:log2(n)
注意:
log2(l000000) ≈ 19.9log2(100000000) ≈ 26.6
二分查找算法描述
用一维数组a存储有序元素序列,用变量low和high分别表示查找范围中第一个元素和最后一个元素的下标,mid表示查找范围的中间位置对应元素的下标,x为要查找的元素。
(1)变量初始化,令low=1,high=n。low和high分别初始化为有序序列的第一个元素和最后一个元素的下标。
(2)判断查找范围low≤high是否成立,如果成立,执行(3),否则输出"-1"(表示没有找到),结束算法。
(3)取中间元素,令mid=(low+high)/2,a[mid]就是中间元素。
(4)比较a[mid]与x,如果a[mid]等于x,则查找成功,结束算法;如果x<a[mid],则在序列的前半部分进行查找,修改查找的上界high=mid-1,下界不变,否则将在序列的后半部分进行在找,修改查找的下界low=mid+1,上界不变,转到(2)。
特别注意:使用二分查找时,必须保证数据是有序的,若数据是无序的,则需要使用排序算法将数据变得有序。
二分查找算法的框架如下:
int ef(int a[],int n,int x)
{
int low=1,high=n,mid;
while(low<=high) //判断查找范围low<=high是否成立
{
mid=(low+high)/2; //取中间元素的位置
if(x==a[mid]) //x已经找到
{
return mid; //返回x对应的下标
}
else if(x<a[mid])
{
high=mid-1; //调整high,在前半部分查找
}
else low=mid+1; //调整low,在后半部分查找
}
return -1;
}
思考:为什么while循环的条件中是“<=”,而不是“<”?
例题1:
二分查找请在一个有序递增数组中(不存在相同元素),采用二分查找,找出值 x的位置,如果 x在数组中不存在,请输出 -1 !
输入数据:
第一行,一个整数 n ,代表数组元素个数(n≤10^6)
第二行,n 个数,代表数组的 n 个递增元素(1≤数组元素值≤10^8 )
第三行,一个整数 x,代表要查找的数(0≤x≤10^8)
输出数据:x 在数组中的位置,或者 -1。
输入数据:
101 3 5 7 9 11 13 15 17 19
3
输出数据:2
解法一:暴力,超时。
#include<bits/stdc++.h>
using namespace std;
int a[1000005];
int main(){
int n,i,f=0,x;
cin>>n;
for(i=1;i<=n;i++)
{ cin>>a[i]; }
cin>>x;
for(i=1;i<=n;i++){
if(a[i]==x){
cout<<i;
f=1; break;
}
}
if(f==0){cout<<-1<<endl;}
}
解法二:二分
#include<bits/stdc++.h>
using namespace std;
int a[1000005];
int main(){
int k,n;
cin >> n;
for(int i=1;i<=n;i++){ cin >> a[i]; }
cin>> k;
int left=1,right=n,mid;
while(left<=right){
mid=(left+right)/2;
if(a[mid]==k){
cout << mid;
return 0;
}else if(k<a[mid]){
right = mid-1;
}else if(k>a[mid]){
left = mid+1;
}
}
cout << -1;
return 0;
}
例题2:查找m个数(tops主题库2646)
题目描述
请你输入一个含 n 个数字的不重复数列,请你高速的在这个数列中寻找 m 个数字 x1,x2,...,xm ,如果能找到直接输出,如果不存在输出 −1−1 ,用换行隔开。
输入格式
输入共 4 行,第一行,为一个数字 n 。第二行为 n 个数字。第三行为一个数字 m 。第四行为 m 个要寻找的数字。
输出格式
输出共 m 行,每行一个数字,如果能找到直接输出原数字,如果找不到,输出 −1−1 。
样例
输入#1
5
1 2 3 4 5
3
2 1 3
输出#1
2
1
3
数据范围与提示
- 0<m,n≤
- 本题卡常,请尽量优化输入与输出
问题分析:
解法一:暴力,样例没问题,提交0分。
#include<bits/stdc++.h>
using namespace std;
int a[1000001];
int main()
{
int n,m,i,j,t;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>a[i];
}
cin>>m;
for(i=1;i<=m;i++)
{
cin>>t;
for(j=1;j<=n;j++)
{
if(a[j]==t)
{
cout<<j<<endl;
break;
}
}
}
return 0;
}
解法2:二分查找,输入超时
#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
int a[N];
int main(){
int n,m,i,j,t;
cin>>n;
for(i=1;i<=n;i++){ cin>>a[i]; }
sort(a+1,a+1+n); cin>>m;
for(i=1;i<=m;i++){
cin>>t;
int left=1,right=n,mid,f=0;
while(left<=right){
mid=(left+right)/2;
if(a[mid]==t){ f=1;break; }
else if(t<a[mid]){ right = mid-1;}
else if(t>a[mid]){ left = mid+1; }
}
if(f==1){ cout <<t<<endl;}
else{ cout << -1<<endl; }
}
}
解法3:数据量比较大的时候,cin输入的方式会输入超时 100分
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[2*N], n, m, q;
int check(int q)
{
int l = 1, r = n;
while(l <= r)
{
int mid = (l+r) >> 1;
if(a[mid] == q)
return q;
else if(a[mid] > q)
r = mid - 1;
else
l = mid + 1;
}
return -1;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++)
scanf("%d", &a[i]);
sort(a+1, a+n+1);
scanf("%d", &m);
for(int i = 1; i <= m; i ++)
{
scanf("%d", &q);
printf("%d\n", check(q));
}
return 0;
}
找朋友
题目描述
小学毕业后,同学们都进入了不同的初中,小明非常想念小伙伴们,所以他打算联系小学的同学们。
现在他得到了市内某所初中的所有名单,找出其中小明的小伙伴们。
输入格式
第一行一个整数n,表示某初中人数。
接下来n行,每行一个字符串,只有小写字母组成,表示该校每个人的拼音。数据保证没有人拼音相同,且已经按照字典序从小到大排序。
第n+2行有一个整数m,表示小明的小伙伴个数。
最后m行,每行一个字符串,只有小写字母组成,表示每个小伙伴的拼音,同样保证没有重复。
输出格式
输出所有在该校的小伙伴的拼音。
每行一个拼音,顺序按照小伙伴给出的顺序。
样例
输入数据#1
3
alice
bob
zhangsan
2
lisi
zhangsan
输出数据#1
zhangsan
说明/提示
【数据范围】
对于70%的数据,n≤1000,m≤100
对于100%的数据,n≤100000,m≤10000,每个人拼音长度不超过15。
所有数据,学校学生名单中的姓名,都是按照字典序从小到大排序。
问题分析:
#include<bits/stdc++.h>
using namespace std;
int m,n;
string a[100005],t;
int fun(string t){
int left=1,right=n,mid;
while(left<=right){
mid=(left+right)/2;
if(a[mid]==t){
return mid;
}else if(t>a[mid]){
left=mid+1;
}else if(t<a[mid]){
right=mid-1;
}
}
return -1;
}
int main(){
cin >> n;
for(int i=1;i<=n;i++){
cin>> a[i];
}
cin >>m;
for(int i=1;i<=m;i++){
cin >> t;
if(fun(t)!=-1){
cout << t << endl;
}
}
return 0;
}
和为给定数(summator)
题目描述
数完正方形,大家决定休息一下,玩个游戏,首先由小天同学给出若干个整数,然后夏同学再给出一个,其它同学在小天同学的整数中查找其中是否有一对数的和等于夏同学给定的数。
输入格式
共三行:
第一行是整数 n(0<n≤100,000) ,表示有 n 个整数。
第二行是 n 个整数。整数的范围是在 00 到 100,000,000100,000,000 之间。(n 个数各不相同)
第三行是一个整数m(0≤m≤2,000,000,000) ,表示需要得到的和。
输出格式
若存在和为 m 的数对,输出两个整数,小的在前,大的在后,中间用单个空格隔开。若有多个数对满足条件,选择数对中较小的数更小的。若找不到符合要求的数对,输出一行"No"。
样例
输入#1
4
2 5 1 4
6
输出#1
1 5
数据范围
- 20%20% 的数据满足 0<n≤100,0<m≤2000
- 50%50% 的数据满足 0<n≤10,000,0<m≤20,000,000
- 100%100% 的数据满足 0<n≤100,000,0<m≤2,000,000,000
问题分析:
解法1:有3个点超时
#include<bits/stdc++.h>
using namespace std;
int a[100001];
int main()
{
int n,i,j;
cin>>n;
long long m;
for(i=1;i<=n;i++)
{ cin>>a[i]; }
sort(a+1,a+1+n);
cin>>m;
if(m>200000000)
{
cout<<"No";
return 0;
}
for(i=1;i<=n;i++)
{
for(j=i+1;j<=n;j++)
{
if(a[i]+a[j]==m)
{
cout<<a[i]<<" "<<a[j];
return 0;
}
}
}
cout<<"No";
}
解法2:数组标记
#include<bits/stdc++.h>
using namespace std;
bool s[100000001];
int main()
{
int n,i;
cin>>n;
long long a,m;
for(i=1;i<=n;i++)
{
cin>>a; s[a]++;
}
cin>>m; //m最大是20亿
if(m>200000000)
{
cout<<"No";return 0;
}
for(i=0;i<=m/2;i++)
{
if(m-i<=100000000&&s[i]!=0&&s[m-i]!=0)
{
cout<<i<<" "<<m-i;
return 0;
}
}
cout<<"No";
}
解法3:二分查找
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int fun(int left,int right,int t){
while(left<=right)
{
int h=(left+right)/2;
if(a[h]==t){ return h; }
if(t<a[h]){right=h-1; }
else if(t>a[h]) {left=h+1;}
}
return -1;
}
int main(){
int n,i,j;
long long m;
cin>>n;
for(i=1;i<=n;i++){ cin>>a[i]; }
sort(a+1,a+1+n);
cin>>m;//m最大是20亿
if(m>2000000000){cout<<"No";return 0;}
for(i=1;i<=n;i++){
if(m-a[i]<=100000000){
int k=m-a[i];
int h=fun(i+1,n,k);
if(h!=-1){
cout<<a[i]<<" "<<a[h];
return 0;
}
}
}
cout<<"No";
}
二分查找左边界
题目描述
请在一个有序不递减的数组中(数组中可能有相等的值),采用二分查找,找到值 x第 1 次出现的位置,如果不存在 x
请输出 -1。
请注意:本题要求出 q个 x,每个 x在数组中第一次出现的位置。
比如有 6 个数,分别是:1,2,2,2,3,3那么如果要求找 3 个数:3,2,5在数组中第一次出现的位置,答案是:5,2,-1
输入格式
第一行,一个整数 n,代表数组元素个数(n≤10^5)
第二行,n个整数,用空格隔开,代表数组的 n 个元素(1≤数组元素的≤10^8 )
第三行,一个整数 q,代表有要求出 q个数首次出现的位置(q≤10^5)
第四行,q 个整数,用空格隔开,代表要找的数(1≤要找的数≤10^8 )
输出格式
输出一行,含 q 个整数,按题意输出要找的每个数在数组中首次出现的位置,如果不存在这样的数,请输出 -1。
输入数据#
16
1 2 2 2 3 3
3
3 2 5
输出数据#
15 2 -1
二分査找左边界注意点:
(1) 二分查找,如果a[mid] == x,还要向左侧看;
(2) 找左边界的本质:找数组中第一个>=X的元素的位置;
(3) 找到位置L之后,要判断a[L]==x (注意,如果都是负数,找0,要判断L在下标范围内)
问题分析:
#include<bits/stdc++.h>
using namespace std;
long long a[100005],n,q,t;
int fun(int x){
int left=1,right=n,mid;
while(left<=right){
mid=(left+right)/2;
if(a[mid]==x){
right=mid-1;
}else if(x<a[mid]){
right=mid-1;
}else if(x>a[mid]){
left=mid+1;
}
}
if(a[left]==x){
return left;
}else{
return -1;
}
}
int main(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
}
cin >> q;
for(int i=1;i<=q;i++){
cin >> t;
cout << fun(t)<< " ";
}
}
二分查找右边界
题目描述
请在一个有序不递减的数组中(数组中可能有相等的值),采用二分查找,找到值 x最后 1次出现的位置,如果不存在 x请输出 -1。
请注意:本题要求出 q个 x,每个 x 在数组中最后一次出现的位置。
比如有 6 个数,分别是:1,2,2,2,3,3那么如果要求找 3 个数:3,2,5,在数组中最后一次出现的位置,答案是:6,4,-1
输入格式:
第一行,一个整数 n,代表数组元素个数(n≤10^5)
第二行,n 个整数,用空格隔开,代表数组的 n 个元素(1≤数组元素的值≤10^8 )
第三行,一个整数 q,代表有要求出 q 个数最后一次出现的位置(q≤10^5)
第四行,q个整数,用空格隔开,代表要找的数(1≤要找的数 ≤10^8)
输出格式:
输出一行,含 q个整数,按题意输出要找的每个数在数组中最后一次出现的位置,如果不存在这样的数,请输出 -1。
输入数据#
16
1 2 2 2 3 3
3
3 2 5
输出数据#
16 4 -1
二分查找右边界注意点:
(1) 二分查找,如果a[mid] == x,还要向右侧看,判断右侧是否还是x;
(2) 找右边界的本质:找的值L,是数组中第一个>x的元素的位置;
(3) 因此要判断L-1 (或者R)的值是否和x相等(a[L-1]==x);
程序如下∶
#include<bits/stdc++.h>
using namespace std;
long long a[100005],n,q,t;
int fun(int x){
int left=1,right=n,mid;
while(left<=right){
mid=(left+right)/2;
if(a[mid]==x){
left=mid+1;
}else if(x<a[mid]){
right=mid-1;
}else if(x>a[mid]){
left=mid+1;
}
}
if(a[left-1]==x){
return left-1;
}else{
return -1;
}
}
int main(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
}
cin >> q;
for(int i=1;i<=q;i++){
cin >> t;
cout << fun(t)<< " ";
}
}
二分函数和答案
binary_search():二分查找函数
binary_search(a+开始, a+结束+1, x, cmp);
函数含义:在a数组的下标区间内,按照cmp的排序规则,找元素x,找到返回true,找不到返回false。
注意点:
(1)查找区间:结束位置后一个位置,和sort()函数一致;
(2)排序规则cmp可以不写默认是升序。如果写的话,查找时的排序规则,必须和排序的规则是一致的;
例子:
#include<bits/stdc++.h>
using namespace std;
int cmp(int x,int y){
return x>y;
}
int main(){
int a[6]={20,10,50,30,60,40};
sort(a+0,a+5+1);
cout << binary_search(a+0,a+5+1,20)<< endl;
cout << binary_search(a+0,a+5+1,36)<<endl;
sort(a,a+6,cmp);
cout << binary_search(a+0,a+5+1,20)<<endl;
cout << binary_search(a+0,a+5+1,20,cmp)<<endl;
return 0;
}
结果:
1
0
0
1
lower_bound():二分査找左边界
lower_bound(a+开始,a+结束+1,x,cmp);
函数含义:在a数组的下标区间内,按照cmp的排序规则,找元素x的左边界(第一个 >=元素x的位置),返回位置指针;(指针(Pointer): 变量的地址,通过它能找到以它为地址的内存单元。)
注意点:
(1)基本注意点同binary_search;
(2)此处返回的不是下标的值,而是返回指针;如果找不到符合条件的元素位置,指向下标为第一个大于元素的位置
例子:
#include<bits/stdc++.h>
using namespace std;
int main(){
int a[6]={20,10,50,20,20,40};
sort(a,a+5+1);//10 20 20 20 40 50
int *p=lower_bound(a+0,a+5+1,20);
// cout << p << " "<< *p << endl;
// cout << p-a<< endl;
cout << lower_bound(a,a+6,20)-a << endl;
cout << lower_bound(a,a+6,15)-a << endl;
cout << lower_bound(a,a+6,60)-a << endl;
return 0;
}
upper_bound:二分查找右边界
upper_bound(a+开始, a+结束+1,x,cmp);
函数含义:在a数组的下标内,按照cmp的排序规则,找元素x的 右边界(第一个 > 元素x的位置),返回位置指针;
注意点:同 lower_bound;
例子:
#include<bits/stdc++.h>
using namespace std;
int main(){
int a[6]={20,10,50,20,20,40};
sort(a,a+5+1);
int *p=upper_bound(a,a+6,20);
cout << p << " "<< *p << endl;
cout << p-a<< endl;
cout << upper_bound(a,a+6,20)-a << endl;
cout << upper_bound(a,a+6,15)-a << endl;
cout << upper_bound(a,a+6,60)-a << endl;
return 0;
}
二分答案
我们可以根据题目的已知条件设定答案的上下界,然后用二分的方法枚举答案,再判断答案是否可行,根据判断的结果逐步缩小答案范围,直到找到符合题目条件的答案为止。二分答案常被用来求解最小值最大或最大值最小等最值问题,将最优化问题转换为判定问题。
适用条件:单调性∶问题的答案具有单调性。枚举可求解∶问题的答案可以通过枚举求解,可以将二分答案理解为枚举的一种优化。
基本步骤:确定答案范围∶确定问题答案可能的最小值和最大值。确定上下界∶确定问题是求上界还是求下界。判定方案∶确定判定方案,编写判定函数(check函数)
例题1:
伐木工题目描述伐木工人小科需要砍倒M米长的木材。这是一个对小科来说很容易的工作,因为他有一个漂亮的新伐木机,可以像野火一样砍倒森林。不过,小科只被允许砍倒单行树木。
小科的伐木机工作过程如下:小科设置一个高度参数H(米),伐木机升起一个巨大的锯片到高度H,并锯掉所有的树比H高的部分(当然,树木不高于H米的部分保持不变)。
例如,如果一行树的高度分别为20,15,10和17,小科把锯片升到15米的高度,切割后树木剩下的高度将是15,15,10和15,而小科将从第1棵树得到5米,从第4棵树得到2米,共得到7米木材。
小科非常关注生态保护,所以他不会砍掉过多的木材。这正是他为什么尽可能高地设定伐木机锯片的原因。帮助小科找到伐木机锯片的最大的整数高度H,使得他能得到木材至少为M米。换句话说,如果再升高1米,则他将得不到M米木材。
输入第1行:2个整数N和M,N表示树木的数量(1<=N<=10^6),M表示需要的木材总长度(1<=M<=2 * 10^9)
第2行:N个整数表示每棵树的高度,值均不超过10^9。所有木材长度之和大于M,因此必有解。;
输出1个整数,表示砍树的最高高度。
样例输入
5 20
4 42 40 26 46
输出36
#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
long long a[N],le=1,r,n,m,mid;
bool check(long long x){
long long s=0;
for(int i=1;i<=n;i++){
if(x<a[i]){
s=s+a[i]-x;
}
if(s>=m){
return true;
}
}
return false;
}
int main(){
cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> a[i];
r = max(a[i],r);
}
while(le<=r){
mid=(le+r)/2;
if(check(mid)==1){
le=mid+1;
}
else{
r = mid-1;
}
}
cout << le-1;
return 0;
}
例题2:
跳石头一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
输入
第一行包含三个整数 L,N,M ,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证L≥1 且 N≥M≥0 。
接下来 N 行,每行一个整数,第 i 行的整数 Di( 0 < Di < L), 表示第 i 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出
一个整数,即最短跳跃距离的最大值。
样例输入
25 5 2
2
11
14
17
21
输出
4
说明
输入输出样例1说明:将与起点距离为 2 和 14 的两个岩石移走后,最短的跳跃距离为 4 (从与起点距离 17 的岩石跳到距离 21 的岩石,或者从距离 21 的岩石跳到终点)。
另:
对于20% 的数据, 0≤M≤N≤10 。
对于 50% 的数据,0≤M≤N≤100 。
对于 100% 的数据, 0≤M≤N≤50,000,1≤L≤1,000,000,000 。
#include<bits/stdc++.h>
using namespace std;
const int N=50100;
int a[N];
int n,m,L;
bool check(int mid){
int c=0,p=0;
for(int i=1;i<=n;i++){
if(a[i]-p<mid){
c++;
}else{
p=a[i];
}
}
if(L-p<mid){
c++;
}
return c<=m;
}
int main(){
cin >> L>> n >> m;
for(int i=1;i<=n;i++){
cin >> a[i];
}
int left = 1,right=L,mid;
while(left<=right){
mid=(left+right)/2;
if(check(mid)){
left=mid+1;
}else{
right = mid-1;
}
}
cout << left-1;//右边界
return 0;
}
二分就讲完了,手快废了,请各位三连谢谢,支持一下博主
祝大家端午快乐!!!!!