二分算法使用前提
数组是有序的,一定是有解的,一定是可以分出边界的。
模板
这里先将模板给出。
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:mid 下取整
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:mid 上取整
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
教大家一种记忆的方法,如果是 r = mid
,则 mid
无需上取整,直接整除 2 即可,即 mid = l + r >> 1
。如果是 l = mid
,则 mid
需要上取整,即 mid = l + r + 1 >> 1
。右移 1 位就是 / 2 ,加号优先级比 右移 要高,所以无需加括号。
至于为什么要这样做,可自行代入一个例子试试。
这里我们一起看一个例子。
因此,只需记住这个模板,就不用再浪费时间考虑边界问题。
题目练习巩固
给定一个按照升序排列的长度为 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
这道题目是连用两次二分,分别求出最左和最右即可,这里直接用套用模板,给出算法实现如下,具体思路注释里写的很清楚:
import java.util.Scanner;
public class Main {
public static int N = (int) (1e5 + 10);
public static int[] a = new int[N];
public static int n;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
int q = sc.nextInt();
for (int i = 0; i < n; i ++)
a[i] = sc.nextInt();
while(q -- > 0){
int x = sc.nextInt();
int l = 0, r = n - 1;
//左端点 a[mid]>=x 就可以区分开
while(l < r){
int mid = l + r >> 1;
// 在左边找 mid可能是答案
if(a[mid] >= x)
r = mid;
else
l = mid + 1;
}
//l == r 就是 >= x的第一个数
if (a[l] == x) System.out.print(l + " ");
else{
System.out.println("-1 -1");
continue;
}
// 找右端点
r = n - 1;
while(l < r){
int mid = l + r + 1 >> 1;
// 区分右端点:a[mid] <= x
if(a[mid] <= x)
// 答案在右边 mid可能是答案
l = mid;
else
r = mid - 1;
}
//直接打印,没有找到的早已 continue
System.out.println(l);
}
sc.close();
}
}
以上就是二分算法的模板,稍微记一下这个区间划分对应的 mid
是上取整还是下取整,就再也不用单独考虑边界问题了!