前言
二分无需数组有序,只需要找一个性质(二段性),能一次刷掉一半的数,就能使用二分。
本文展示二分模版应用,很多题的最优解并不是二分,甚至有二分解比最优解还要难想的情况。但是为了加强对二分使用,都强制使用了二分(为了二分而二分)。
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r >> 1; // 当下面出现 mid - 1 时,这里的 mid = l + r + 1 >> 1
if (check(mid)) l = mid + 1; // l 和 r 的取值看下面固定搭配
else r = mid;
}
固定搭配
l = mid
搭配r = mid - 1
l = mid + 1
搭配r = mid
r = mid
搭配l = mid + 1
r = mid + 1
搭配l = mid
至于 4 种选哪一种?
当符合条件时
- 是你要的还是不要的?是你要的,那就必须
l = mid
或r = mid
- 是要扩大还是缩小?扩大:
l = mid
或l = mid + 1
固定搭配不唯一,可以通过改变 check
,来改变固定搭配的选择
当 mid = l + r + 1 >> 1
时,我们会发现很难使用 l + (r - l) / 2
避免溢出时
可以选择改变 check
,将固定搭配转换为不存在 mid - 1
的情况,这样就能直接使用 l + (r - l) / 2
704. 二分查找
class Solution {
public int search(int[] nums, int x) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = l + r >> 1;
if (nums[mid] >= x) r = mid;
else l = mid + 1;
}
return nums[l] == x ? l : -1;
}
}
374. 猜数字大小
public class Solution extends GuessGame {
public int guessNumber(int n) {
if (n == 1) return 1;
int l = 1, r = n;
while (l < r) {
int mid = l + (r - l) / 2;
if (guess(mid) <= 0) r = mid;
else l = mid + 1;
}
return l;
}
}
278. 第一个错误的版本
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
int l = 1, r = n;
while (l < r) {
int mid = l + (r - l) / 2;
if (isBadVersion(mid)) r = mid;
else l = mid + 1;
}
return l;
}
}
367. 有效的完全平方数
class Solution {
public boolean isPerfectSquare(int num) {
long l = 0, r = num;
while (l < r) {
long mid = l + r >> 1;
if (mid * mid < num) l = mid + 1;
else r = mid;
}
return l * l == num;
}
}
69. x 的平方根
class Solution {
public int mySqrt(int x) {
if (x < 2) return x;
long l = 0, r = x;
while (l < r) {
long mid = l + r >> 1;
if (mid * mid > x) r = mid;
else l = mid + 1;
}
return (int)(l - 1);
}
}
441. 排列硬币
class Solution {
public int arrangeCoins(int n) {
long l = 0, r = n;
while (l < r) {
long mid = l + r + 1 >> 1;
if (mid * (1 + mid) / 2 > n) r = mid - 1;
else l = mid;
}
return (int)l;
}
}
852. 山脉数组的峰顶索引
class Solution {
// 找到数组中最大元素
// 设:当前元素为 arr[i],最大元素为 arr[j]
// arr[i] < arr[i + 1],i < j
// arr[i] > arr[i + 1],i >= j
public int peakIndexInMountainArray(int[] arr) {
int l = 0, r = arr.length - 1;
while (l < r) {
int mid = l + r >> 1;
if (arr[mid] < arr[mid + 1]) l = mid + 1;
else r = mid;
}
return l;
}
}
744. 寻找比目标字母大的最小字母
class Solution {
public char nextGreatestLetter(char[] letters, char target) {
int l = 0, r = letters.length - 1;
if (letters[r] <= target) return letters[0];
while (l < r) {
int mid = l + r >> 1;
if (letters[mid] > target) r = mid;
else l = mid + 1;
}
return letters[l];
}
}
1539. 第 k 个缺失的正整数
class Solution {
public int findKthPositive(int[] arr, int k) {
if (arr[0] > k) return k;
int l = 0, r = arr.length;
while (l < r) {
int mid = (l + r) >> 1;
int x = mid < arr.length ? arr[mid] : Integer.MAX_VALUE;
if (x - mid - 1 >= k) r = mid;
else l = mid + 1;
}
return k - (arr[l - 1] - (l - 1) - 1) + arr[l - 1];
}
}
35. 搜索插入位置
class Solution {
public int searchInsert(int[] q, int x) {
int l = 0, r = q.length - 1;
if (x <= q[0]) return 0;
// 找到小于 x 的最后一个元素,那么下一个位置就是i插入位置
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] < x) l = mid;
else r = mid - 1;
}
return l + 1;
}
}
153. 寻找旋转排序数组中的最小值
class Solution {
public int findMin(int[] q) {
// 找到最后一个最大元素,最大元素的下一个就是最小元素
int l = 0, r = q.length - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] >= q[0]) l = mid;
else r = mid - 1;
}
return l + 1 < q.length ? q[l + 1] : q[0];
}
}
154. 寻找旋转排序数组中的最小值 II
class Solution {
public int findMin(int[] q) {
int n = q.length;
// 0 1 2 2 2 -> 2 2 0 1 2 -> 2 2 0 1,去掉尾巴与头重复的元素
while (n > 1 && q[0] == q[n - 1]) n--;
// 找到最后一个最大元素,最大元素的下一个就是最小元素
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r + 1>> 1;
if (q[mid] >= q[0]) l = mid;
else r = mid - 1;
}
return l + 1 < n ? q[l + 1] : q[0];
}
}
33. 搜索旋转排序数组
class Solution {
public int search(int[] q, int x) {
int n = q.length;
if (n == 0) return -1;
// 找到旋转点(数组中最大的元素)
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] >= q[0]) l = mid;
else r = mid - 1;
}
// 选择对应区间
if (x >= q[0]) l = 0;
else {
l = r + 1;
r = n - 1;
}
// 有序区间二分
while (l < r) {
int mid = l + r >> 1;
if (q[mid] >= x) r = mid;
else l = mid + 1;
}
return q[r] == x ? l : -1;
}
}
81. 搜索旋转排序数组 II
class Solution {
public boolean search(int[] q, int x) {
int n = q.length;
if (n == 0) return false;
// 0 0 1 2 3 -> 0 1 2 3 0 -> 0 1 2 3,去掉尾巴与头重复的元素
while (n > 1 && q[0] == q[n - 1]) n--;
// 找到旋转点
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] >= q[0]) l = mid;
else r = mid - 1;
}
// 选择对应区间
if (x >= q[0]) l = 0;
else {
l++;
r = n - 1;
}
// 有序区间二分
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] > x) r = mid - 1;
else l = mid;
}
return q[r] == x;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {
public int[] searchRange(int[] q, int x) {
int a = le(q, x), b = ge(q, x);
if (a >= 0 && a < q.length && q[a] != x) a++;
if (b >= 0 && b < q.length && q[b] != x) b--;
if (a < 0 || a >= q.length || b < 0 || b >= q.length || q[a] != x || q[b] != x) return new int[] {-1, -1};
return new int[] {a, b};
}
// 小于等于 x 最大的元素下标
public int le(int[] q, int x) {
int l = 0, r = q.length - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] < x) l = mid;
else r = mid - 1;
}
return l;
}
// 大于等于 x 最小的元素下标
public int ge(int[] q, int x) {
int l = 0, r = q.length - 1;
while (l < r) {
int mid = l + r >> 1;
if (q[mid] > x) r = mid;
else l = mid + 1;
}
return l;
}
}
1385. 两个数组间的距离值
class Solution {
public int findTheDistanceValue(int[] a, int[] b, int d) {
Arrays.sort(b);
int res = 0;
for (int i = 0; i < a.length; i++) {
int x = ge(a[i], b);
int y = lt(a[i], b);
res += Math.abs(x - a[i]) > d && Math.abs(y - a[i]) > d ? 1 : 0;
}
return res;
}
public int ge(int x, int[] a) {
int l = 0, r = a.length - 1;
while (l < r) {
int mid = l + r >> 1;
if (a[mid] >= x) r = mid;
else l = mid + 1;
}
return a[l];
}
public int lt(int x, int[] a) {
int l = 0, r = a.length - 1;
while (l < r) {
int mid = l + r + 1>> 1;
if (a[mid] < x) l = mid;
else r = mid - 1;
}
return a[l];
}
}
162. 寻找峰值
class Solution {
public int findPeakElement(int[] q) {
int l = 0, r = q.length - 1;
// 值变大了一定会产生峰顶
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid - 1] > q[mid]) r = mid - 1;
else l = mid;
}
return l;
}
}
209. 长度最小的子数组
class Solution {
public int minSubArrayLen(int x, int[] q) {
int n = q.length;
// 前缀和:sum[i] = q[0] + ... + q[i - 1]
int[] sum = new int[n + 1];
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + q[i - 1];
}
int res = Integer.MAX_VALUE;
for (int i = 0; i <= n; i++) {
int l = i, r = n;
while (l < r) {
int mid = l + r >> 1;
if (sum[mid] - x >= sum[i]) r = mid;
else l = mid + 1;
}
if (sum[l] - x >= sum[i]) res = Math.min(res, l - i);
}
return res == Integer.MAX_VALUE ? 0 : res;
}
}
222. 完全二叉树的节点个数
class Solution {
public int countNodes(TreeNode root) {
if (root == null) return 0;
// 1. 求完全二叉树的高度 logN
int h = 0;
for (TreeNode p = root; p != null; p = p.left) h++;
// 2. 二分猜某个节点不存在 logN
// 完全二叉树的节点个数在 2^(h - 1) 到 2^h - 1 之间
int l = 1 << (h - 1), r = (1 << h) - 1;
while (l < r) {
int mid = l + r >> 1;
if (check(mid, root)) l = mid + 1;
else r = mid;
}
return check(l, root) ? l : --l;
}
// 3. 验证某个节点存在 logN
public boolean check(int x, TreeNode node) {
int cnt = 0;
for (int i = x; i > 0; i >>= 1) cnt++;
int[] q = new int[--cnt];
for (int i = 0; x > 1; i++, x >>>= 1) {
q[i] = x & 1;
}
for (int i = cnt - 1; i >= 0; i--) {
if (q[i] == 1) node = node.right;
else node = node.left;
if (node == null) return false;
}
return true;
}
}
259. 较小的三数之和
class Solution {
public int threeSumSmaller(int[] nums, int target) {
Arrays.sort(nums);
int res = 0;
for (int i = 0; i < nums.length - 2; i++) {
int l = i + 1, r = nums.length - 1;
// 转换为俩数之和
while (l < r) {
if (nums[l] + nums[r] < target - nums[i]) {
res += r - l;
l++;
} else r--;
}
}
return res;
}
}
349. 两个数组的交集
class Solution {
public int[] intersection(int[] a, int[] b) {
Arrays.sort(a);
Arrays.sort(b);
int n = a.length, m = b.length, idx = 0;
int[] c = new int[n];
for (int i = 0; i < n; i++) {
int l = 0, r = m - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (b[mid] > a[i]) r = mid - 1;
else l = mid;
}
if (idx > 0 && c[idx - 1] == a[i]) continue;
if (a[i] == b[l]) c[idx++] = a[i];
}
return Arrays.copyOf(c, idx);
}
}
350. 两个数组的交集 II
class Solution {
public int[] intersect(int[] a, int[] b) {
Arrays.sort(a);
Arrays.sort(b);
int n = a.length, m = b.length, idx = 0;
int[] c = new int[n];
for (int i = 0, l = 0; i < n; i++) {
int r = m - 1;
while (l < r) {
int mid = l + r >> 1;
if (b[mid] < a[i]) l = mid + 1;
else r = mid;
}
if (l < m && a[i] == b[l]) {
c[idx++] = a[i];
l++;
}
}
return Arrays.copyOf(c, idx);
}
}
475. 供暖器
public class Solution {
public int findRadius(int[] hs, int[] ht) {
Arrays.sort(ht);
int n = hs.length, m = ht.length;
int res = 0;
for (int i = 0; i < n; i++) {
int min = 0;
if (hs[i] < ht[0]) min = ht[0] - hs[i];
else if (hs[i] > ht[m - 1]) min = hs[i] - ht[m - 1];
else {
// 找最后一个比 hs[i] 小的 ht[j],那么 ht[j + 1] 一定大于 hs[i]
// 这样 hs[i] 夹在 ht[j] 和 ht[j + 1] 之间
int l = 0, r = m - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (ht[mid] < hs[i]) l = mid;
else r = mid - 1;
}
min = Math.abs(ht[l] - hs[i]);
if (l + 1 < m) min = Math.min(min, Math.abs(ht[l + 1] - hs[i]));
}
res = Math.max(res, min);
}
return res;
}
}
540. 有序数组中的单一元素
class Solution {
public int singleNonDuplicate(int[] q) {
// 将一个一个元素构成的数组,看成一对一对构成的数组
int n = q.length, l = 0, r = (n - 1) >> 1;
if (n == 1) return q[0];
if (q[0] != q[1]) return q[0];
// 找到满足对内俩元素相等的最后一个对,那么这个对的下一个对就一定是不同元素所构成的对
while (l < r) {
int mid = l + r + 1 >> 1;
if (mid * 2 + 1 >= n) break;
if (q[mid * 2] != q[mid * 2 + 1]) r = mid - 1;
else l = mid;
}
return q[2 * (l + 1)];
}
}
633. 平方数之和
class Solution {
public boolean judgeSquareSum(int c) {
long l = 0, r = c;
// 开方
while (l < r) {
long mid = l + r + 1 >> 1;
if (mid * mid <= c) l = mid;
else r = mid - 1;
}
// 枚举答案
for (int i = 0; i <= l; i++) {
int a = 0, b = (int)l;
while (a < b) {
int mid = a + b + 1 >> 1;
if (mid * mid <= c - i * i) a = mid;
else b = mid - 1;
}
if (a * a == c - i * i) return true;
}
return false;
}
}
1337. 矩阵中战斗力最弱的 K 行
class Solution {
public int[] kWeakestRows(int[][] g, int k) {
int n = g.length, m = g[0].length;
Queue<int[]> q = new PriorityQueue<>((a, b) -> a[1] != b[1] ? a[1] - b[1] : a[0] - b[0]);
for (int i = 0; i < n; i++) {
int l = 0, r = m - 1;
while (l < r) {
int mid = l + r >> 1;
if (g[i][mid] > 0) l = mid + 1;
else r = mid;
}
if (l == m - 1 && g[i][l] == 1) l++;
q.add(new int[] {i, l});
}
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = q.poll()[0];
}
return res;
}
}
1346. 检查整数及其两倍数是否存在
class Solution {
public boolean checkIfExist(int[] q) {
Arrays.sort(q);
int n = q.length;
for (int i = 0; i < n - 1; i++) {
int l = i + 1, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] > 2 * q[i]) r = mid - 1;
else l = mid;
}
if (q[l] == 2 * q[i]) return true;
l = i + 1;
r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[i] < 2 * q[mid]) r = mid - 1;
else l = mid;
}
if (q[i] == 2 * q[l]) return true;
}
return false;
}
}
1855. 下标对中的最大距离
class Solution {
// 非递增,i <= j && a[i] <= b[j],d = j - i
public int maxDistance(int[] a, int[] b) {
int n = a.length, m = b.length;
int res = 0;
// 找最后一个大于等于 a[i] 的 b[j]
for (int i = 0; i < n; i++) {
int l = 0, r = m - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (b[mid] >= a[i]) l = mid;
else r = mid - 1;
}
if (i <= l) res = Math.max(res, l - i);
}
return res;
}
}