概要
谨以博客记录我的算法学习之旅,这是我学习的第一个算法,二分,这个算法虽然看起来简单,但是极其容易出错,模板只是起到辅助的作用,如果想真正搞懂,一定要在模板的基础上刷题和自己动手去模拟,这样的时间长了后即使不需要模板也可以信手拈来了。
为什么要引入二分
顺序查找的速度属实太慢,分分钟超时,所以需要二分来加快速度
何为二分?
二分大概就是在一个数组有序的情况下,每次都查找中间的值,判断其大小,如果大于,则找其左边的,如果小于,则找其右边的,话不多说,我们先上模板
二分模板
//查找左边界 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;
}
作者:CPT1024
链接:https://www.acwing.com/solution/content/107848/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这个是我在acwing上看到的一位大佬写的模板,看起来复杂而又不复杂,哈哈哈我在说什么呀,我大概解释一下,SL函数就是查找其左边界(即从左边开始第一个大于等于需要找的值的那个位置,SR函数则是查找其右界(即最后一个大于等于需要找的那个值的位置,当然两边也可以换成小于,根据题目来区分,不过这些需要多多做题练习,模板为啥要这样子做呢?且听我分析。
*SL:也就是刚刚说的找从左边开始第一个大于等于ans的位置,那么这里的check(mid)就应该改为a[mid]>=ans,而为什么这里要等于并且下面的l要加1呢,原因是因为既然我们要找第一个大于等于它的数字,那么我们就希望我们现在middle的位置在尽可能左边一点,只有这样子才能找到第一个,所以如果a[middle]<ans的话,l=middle+1,因为a[middle]已经小于ans了,那么这个middle不要也罢,直接从它的下一位开始判断,而另外一边为什么要取等于号呢?因为只有这样子才能确保那个等于ans的middle我们找到了,而且在把这个我们找到的值往左边缩小,一直缩小到我们需要的那个最开始的值。
SR:SR就不用多说了,为什么要除以二那里要加1呢,原因就是计算机中的整形除法会丢精度,这样会陷入死循环,大家可以自己去试试,其他的不赘述,跟上面的差不多。
话不多说,我们来做一道题。
题目以及题解
题解(套模板即可):
import java.util.Scanner;
public class p789 {
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int q=sc.nextInt();
int []a=new int[n];
for (int i = 0; i < n; i++) {
a[i]=sc.nextInt();
}
while (q-->0){
int start=0;
int end=a.length-1;
int ans=sc.nextInt();
while(start<end){ //查找第一个相等的
int middle=(start+end)>>1;
if(a[middle]>=ans) end=middle;
else start=middle+1;
}
if(a[start]!=ans){
System.out.printf("%d %d",-1,-1);
System.out.println();
}
else {
int num1=start;
start=0;
end=a.length-1;
while(start<end){ //求最后一个,也就是右边界
int middle=(start+end+1)>>1;
if(a[middle]<=ans) start=middle;
else end=middle-1;
}
System.out.printf("%d %d",num1,end);
System.out.println();
}
}
}
}
小结
大家多多练习吧,我只是一个小白,不太会表达,写的不好的地方请多多指教,谢谢,希望能帮助到大家。