题目描述
给定一个按照升序排列的长度为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
算法1
(二分 - 1) – Y 总模板:
本题是要寻找查询的一个数的范围: 返回的是一个区间值: [l, r]。
我们可以分别把 l 和 r 看作是区间的左端点和右端点,即:
-
l 是所有满足等于 x 的 最小值
-
r 是所有满足等于 x 的最大值
那么我们再来看两种模板:
[l, mid] 和 [mid + 1, r]
[l, mid - 1] 和 [mid, r]
mid
值是我们想求的值,所以在写 check(mid)
时就要想好条件,
以此题为例: l : 最小值 >= x, r : 最大值 <= x
这种极值是最绕的,理解这一步,其实模板就可以用得很灵活,不必想哪个是第一个,哪个是第二个。
求 l :
public static int bf(int[] a, int l, int r, int x) {
while (l < r) {
int mid = l + r >> 1;
if (a[mid] >= x) {
r = mid;
} else {
l = mid + 1;
}
}
if (a[r] == x) {
return r;
}
return -1;
}
求 l 这里我用的是[l, mid], [mid + 1, r] 这个区间,通过不断二分[l, mid], 找出符合条件的 l
求 r :
public static int bs(int[] a, int l, int r, int x) {
while (l < r) {
int mid = l + r + 1 >> 1;
if (a[mid] <= x) {
l = mid;
} else {
r = mid - 1;
}
}
if (a[l] == x) {
return l;
}
return -1;
}
同,如上。
完整代码
import java.util.*;
public class Main {
public static final int N = 100010;
public static int[] arr = new int[N];
public static int bf(int[] a, int l, int r, int x) {
while (l < r) {
int mid = l + r >> 1;
if (a[mid] >= x) {
r = mid;
} else {
l = mid + 1;
}
}
if (a[r] == x) {
return r;
}
return -1;
}
public static int bs(int[] a, int l, int r, int x) {
while (l < r) {
int mid = l + r + 1 >> 1;
if (a[mid] <= x) {
l = mid;
} else {
r = mid - 1;
}
}
if (a[l] == x) {
return l;
}
return -1;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int q = in.nextInt();
for (int i = 0; i < n; i++) {
arr[i] = in.nextInt();
}
while (q > 0) {
int x = in.nextInt();
int l = bf(arr, 0, n - 1, x);
int r = bs(arr, 0, n - 1, x);
System.out.println(l + " " + r);
q--;
}
}
}
算法2
(二分 - 2)
(套娃)本题是要寻找查询的一个数的范围: 返回的是一个区间值: [l, r]。
我们可以分别把 l 和 r 看作是区间的左端点和右端点,即:
-
左端点是所给数组中第一个等于 x 的数
-
右端点是所给数组中最后一个等于 x 的数
第二种其实不用考虑这么多,但是写起来感觉不够优雅,但是很好理解,第一种 Y 总的是考虑 check(mid) 与 mid 的关系,
第二种实际上只要想明白是第一个重复的值还是最后一个就好了。
完整代码
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String[] arrNum = reader.readLine().split(" ");
int n = Integer.parseInt(arrNum[0]);
int[] arr = new int[n];
int q = Integer.parseInt(arrNum[1]);
String[] arrStr = reader.readLine().split(" ");
for (int i = 0; i < n; i++) {
arr[i] = Integer.parseInt(arrStr[i]);
}
while (q > 0) {
int t = Integer.parseInt(reader.readLine());
String res = bFirst(arr, t) + " " + bSecond(arr, t);
System.out.println(res);
q--;
}
reader.close();
}
private static int bFirst(int[] a, int value) {
int low = 0;
int n = a.length;
int high = n - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a[mid] == value) {
if (mid == 0 || a[mid - 1] != value) {
return mid;
} else {
high = mid - 1;
}
} else if (a[mid] < value) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
private static int bSecond(int[] a, int value) {
int low = 0;
int n = a.length;
int high = n - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a[mid] == value) {
if (mid == n - 1 || a[mid + 1] != value) {
return mid;
} else {
low = mid + 1;
}
} else if (a[mid] < value) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
}
blablabla