文章目录
本文通过两道例题分别讲解整数二分
和小数二分
,其中整数二分更具难度一些。
有单调性一定可以二分,可以二分不一定有单调性。即没有单调性可能也可以二分。
二分的本质是:边界! (寻找边界点)
数的范围(整数二分)
题目描述
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。
数据范围
1 ≤ n ≤ 100000
1 ≤ q ≤ 10000
1 ≤ k ≤ 10000
解法
代码1——写两个二分
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), q = sc.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = sc.nextInt();
}
int k, l, r, mid;
while (q-- > 0) {
k = sc.nextInt();
l = 0;
r = n - 1;
while (l < r) {
mid = l + r >> 1;
if (nums[mid] >= k) {
r = mid;
} else {
l = mid + 1;
}
}
if (nums[l] != k) {
System.out.println("-1 -1");
} else {
System.out.print(l + " ");
l = 0;
r = n - 1;
while (l < r) {
mid = l + r + 1 >> 1;
if (nums[mid] <= k) {
l = mid;
} else {
r = mid - 1;
}
}
System.out.println(l);
}
}
}
}
代码2——写一个二分lowBound()
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(), q = scanner.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; ++i) a[i] = scanner.nextInt();
while (q-- != 0) {
int t = scanner.nextInt();
int l = lowBound(a, t), r = lowBound(a, t + 1) - 1;
if (l == n || a[l] != t) System.out.println("-1 -1");
else System.out.println(l + " " + r);
}
}
// 求第一个>=t的下标
public static int lowBound(int[] a, int t) {
int l = 0, r = a.length;
while (l < r) {
int mid = l + r >> 1;
if (a[mid] < t) l = mid + 1;
else r = mid;
}
return l;
}
}
讲解
其实题目很简单,就是在每个查询中(总共q个查询,就是q次循环),查找到指定元素的开始位置和结束位置,这里使用二分法来查找。
q次查询的复杂度是O(N),二分查找的复杂度是O(logN)。
在上面的代码中,属于二分精华的有两部分,分别是:
寻找左边界:
while (l < r) {
mid = l + r >> 1;
if (nums[mid] >= k) {
r = mid;
} else {
l = mid + 1;
}
}
寻找右边界:
while (l < r) {
mid = l + r + 1 >> 1;
if (nums[mid] <= k) {
l = mid;
} else {
r = mid - 1;
}
}
关于二分模板可以查看:https://blog.csdn.net/qq_43406895/article/details/126787887
我们利用模板来进行分析:
左端点:当 mid 位置 < target时,应该向 mid 的右移,当 mid 位置 >= target 时,r 指针变成 mid 就可以了。
右端点:当 mid 位置 > target时,应该向 mid 的左移,当 mid 位置 <= target 时,l 指针变成 mid 就可以了。
数的三次方根(小数二分)
题目描述
给定一个浮点数 n,求它的三次方根。
数据范围
−10000 ≤ n ≤ 10000
解法
注意这里 l 和 r 的初始值分别是 -100 和 100。
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double n = sc.nextDouble();
double l = -100;
double r = 100;
while (r - l > 1e-8) {
double mid = (l + r) / 2;
if (Math.pow(mid, 3) >= n) {
r = mid;
} else {
l = mid;
}
}
System.out.print(String.format("%.6f", l));
}
}
为什么不能写成double l = -Math.abs(n), r = Math.abs(n);(左右边界初始值)
对于样例:
即左右边界的绝对值可能是大于 n 的!
写成 double l = -Math.abs(n) - 1, r = Math.abs(n) + 1;
是可行的。
讲解
浮点数二分就简单多了,永远是double mid = (l + r) / 2;
,r = mid;
和l = mid;
,结果大了就减小r,小了就增大l。
值得注意的是,while(l < r)
变成了while (r - l > 1e-8)
,这是因为浮点数的计算精度误差导致的。
相关链接:
【算法】二分查找经典模板