二分查找 - 手写模板与自带函数
手写模板
因为临界值有左右两侧,所以二分可以分为两种类别:求最后一个不满足要求的数,求第一个满足要求的数。
模板
手写模板很多,这里推荐一种最无脑的,无需注意开闭区间的,大部分情况不用在多个模板里切换的万能模板:
int l=0,r=n,mid; //左闭右闭区间
while(l<r) //直到左闭右闭区间只有一个数
{
mid=(l+r)>>1; //位运算加速
if(条件)r=mid; //如果满足条件,就说明 r 可能是答案,但 r 后面的不可能是答案
else l=mid+1; //否则就说明 l 和 l 左边的都不可能是答案
}
cout<<l; //这里输出 l 和 r 都是一样的
这个求的是第一个满足“条件”的数。区间为左闭右闭,答案输出
对于求第一个满足要求的数,直接套用该模板。
而对于求最后一个不满足要求的数,套用这个模板后答案就是
唯一要动脑的地方就是写“条件”。一定要想清楚等于的情况到底是缩小左边还是右边。
无注释版:
int l=,r=,mid;
while(l<r)
{
mid=(l+r)>>1;
if()r=mid;
else l=mid+1;
}
cout<<l;
坑点
这种写法也有缺陷,就是二分如果找不到方案,会自动指向最后一个元素,即
此时如果是要
为了解决这个问题,我们可以在
构造那个极大值,既可以通过手动构造极大值的方式,也可以在 check 里加一个特判。这里推荐在 check 里直接加 mid==n+1 的特判。( 不是 r+1 )
应用实例
二分查找模板,包含了第一个满足要求的、最后一个不满足要求的、无解情况的判断。
(忽略“下标从
#include <bits/stdc++.h>
using namespace std;
int n,q,a[100005],x;
int find_start(int p)
{
int l=1,r=n+1,mid;
while(l<r)
{
mid=(l+r)>>1;
if(a[mid]>=p||mid==n+1)r=mid; //特判新加的点始终为 true,这里注意是 n+1 不是 r+1
else l=mid+1;
}
if(a[l]!=p)return -1; //判断是否合法,而不是判断是否指向新加的点
return l;
}
int find_end(int p)
{
int l=1,r=n+1,mid;
while(l<r)
{
mid=(l+r)>>1;
if(a[mid]>p||mid==n+1)r=mid; //特判新加的点始终为 true,这里注意是 n+1 不是 r+1
else l=mid+1;
}
l--; //变成求最后一个不满足的
if(a[l]!=p)return -1; //判断是否合法,而不是判断是否指向新加的点
return l;
}
int main()
{
cin>>n>>q;
for(int i=1;i<=n;i++)cin>>a[i];
while(q--)
{
cin>>x;
cout<<find_start(x)<<' '<<find_end(x)<<endl;
}
return 0;
}
find_end()函数也可以这么写:
int find_end(int p)
{
int l=1,r=n,mid;
while(l<r)
{
mid=(l+r+1)>>1;
if(a[mid]<=p)l=mid;
else r=mid-1;
}
if(a[l]!=p)return -1;
return l;
}
查找最后一个满足要求的数的模板
int l=1,r=n,mid;
while(l<r)
{
mid=(l+r+1)>>1;
if(check(mid))l=mid;
else r=mid-1;
}
return l;
自带函数
lower_bound()
基础用法:返回下标:lower_bound(a+1,a+n+1,val,cmp)-a
不加 cmp 时:返回第一个大于等于
加 cmp 时 :返回第一个 false 时的下标。
cmp 函数写法:bool cmp(const int &e,const int &val){}
#include <bits/stdc++.h>
using namespace std;
int n,a[100005];
bool cmp(const int &e,const int &val)
{
return e>=val;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
int t;
cin>>t;
while(t--)
{
int x;
cin>>x;
cout<<lower_bound(a+1,a+n+1,x,cmp)-a<<endl;
}
return 0;
}
这个代码写的是:一个从大到小的有序数组中,第一个小于
upper_bound()
基础用法:返回下标:upper_bound(a+1,a+n+1,val,cmp)-a
不加 cmp 时:返回第一个大于
加 cmp 时 :返回第一个 true 时的下标。
cmp 函数写法:bool cmp(const int &val,const int &e){}
#include <bits/stdc++.h>
using namespace std;
int n,a[100005];
bool cmp(const int &val,const int &e)
{
return e<val;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
int t;
cin>>t;
while(t--)
{
int x;
cin>>x;
cout<<upper_bound(a+1,a+n+1,x,cmp)-a<<endl;
}
return 0;
}
这个代码写的是:一个从大到小的有序数组中,第一个小于
总结
在 upper_bound 与 lower_bound 中,更推荐使用 upper_bound ,因为他返回的是第一个 true 的元素,写 cmp 更加容易且直观。
唯一要注意的是,upper_bound 的 cmp 函数
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战