刷透二分:(一)二分模版应用

前言

二分无需数组有序,只需要找一个性质(二段性),能一次刷掉一半的数,就能使用二分。

本文展示二分模版应用,很多题的最优解并不是二分,甚至有二分解比最优解还要难想的情况。但是为了加强对二分使用,都强制使用了二分(为了二分而二分)。

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; 
}

固定搭配

  1. l = mid 搭配 r = mid - 1
  2. l = mid + 1 搭配 r = mid
  3. r = mid 搭配 l = mid + 1
  4. r = mid + 1 搭配 l = mid

至于 4 种选哪一种?

当符合条件时

  1. 是你要的还是不要的?是你要的,那就必须 l = midr = mid
  2. 是要扩大还是缩小?扩大:l = midl = 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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值