leetcode---二分查找

275. H指数 II

在这里插入图片描述在这里插入图片描述
【代码一】通过----二分

class Solution {
    public int hIndex(int[] citations) {
        return bi(citations, 0, citations.length-1);
    }
    private int bi(int[] a, int left, int right){
        int l = left; // 左边界,不动
        int r = right; // 右边界,不动
        int res = 0;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] >= right-mid+1){ // a[mid]~a[right]都大于等于right-mid+1
                    // h至少应该是right-mid+1,继续往左搜索,看是否h可以继续增大
                res = right-mid+1; // 保存每次的可能解,搜索最终的res就是题解
                r = mid - 1;
            }else{
                l = mid + 1;
            }
        }
        return res;
    }
}

287. 寻找重复数

在这里插入图片描述
【代码一】通过—二分查找(解法不具有一般性)

class Solution {
    public int findDuplicate(int[] nums) {
        // 题目关键点:所有数范围是[1, n],且有n+1个数。。。
        // 举例:如果n=3,重复的数是2,排序后数组可能是2,2,2,2/2,2,2,3/1,2,2,2/1,2,2,3
        // 下面的二分思考下是没问题的。
        // 但是如果把题目改动下,把n+1个数改成n个数,下面的二分就错了
        // 因为,n=3,此时重复数为2,排序后数组可能是2,2,2/2,2,3/1,2,2
        int l = 1;
        int r = nums.length-1;
        int res = -1;
        // 其实就是对[1, n]二分
        while(l <= r){
            int mid = l + ((r-l)>>1);
            int cnt = 0;
            for(int i = 0; i < nums.length; i++){
                if(nums[i] <= mid){
                    cnt++;
                }
            }
            if(cnt <= mid){
                l = mid + 1;
            }else{
                res = mid; // 记录可能的题解
                r = mid - 1;
            }
        }
        return res;
    }
}

【代码二】通过—Floyd快慢指针判环(解法更具一般性)

class Solution {
    public int findDuplicate(int[] nums) {
        // 比如数组nums[] = {2,1,3,1},
        // nums[0]=2,nums[2]=3,nums[3]=1,nums[1]=2出现环了。。
        // 搞两个指针,快指针速度是满指针的二倍,如果有环,两个指针必定相遇,环中追击必定遇到
        // 设链表头是S,入环位置是C,环中相遇位置是M。|SC|=m,|CM|=k,环长度是N
        // 慢指针行走距离:s = m + k + N*a
        // 快指针行走距离:2s = m + k + N*b
        // 2s - s = s = (b-a)*N = m + k + N*a, m+k是N的整数倍
        // 两指针相遇后,把其中一个指针p挪到S,另一个指针q在相遇点M,两指针同时运动,一次都是一步,
        // 当p指针到C时,走了m步,q当然也走m步,q从M走了m步,就相当于q从C出发走了m+k步,而m+k是
        // 环长度N的整数倍,那么此时q必定在C处与p相遇。证毕。
        int p = 0;
        int q = 0;
        do{
            p = nums[p];
            q = nums[nums[q]];
        }while(p != q);
        p = 0;
        while(p != q){
            p = nums[p];
            q = nums[q];
        }
        return p;
    }
}

【代码三】通过—Set/Map解法(解法不合题目要求)

class Solution {
    public int findDuplicate(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for(int i = 0; i < nums.length; i++){
            if(set.contains(nums[i])){
                return nums[i];
            }
            set.add(nums[i]);
        }
        return -1;
    }
}

【代码四】通过—排序(解法不合题目要求)

class Solution {
    public int findDuplicate(int[] nums) {
        Arrays.sort(nums);
        for(int i = 1; i < nums.length; i++){
            if(nums[i-1] == nums[i]){
                return nums[i];
            }
        }
        return -1;
    }
}

【代码五】通过—暴力(解法不合题目要求)

class Solution {
    public int findDuplicate(int[] nums) {
        for(int i = 0; i < nums.length; i++){
            for(int j = i+1; j < nums.length; j++){
                if(nums[i] == nums[j]){
                    return nums[i];
                }
            }
        }
        return -1;
    }
}

1237. 找出给定方程的正整数解在这里插入图片描述在这里插入图片描述

【代码一】通过—暴力

/*
 * // This is the custom function interface.
 * // You should not implement it, or speculate about its implementation
 * class CustomFunction {
 *     // Returns f(x, y) for any given positive integers x and y.
 *     // Note that f(x, y) is increasing with respect to both x and y.
 *     // i.e. f(x, y) < f(x + 1, y), f(x, y) < f(x, y + 1)
 *     public int f(int x, int y);
 * };
 */
class Solution {
    public List<List<Integer>> findSolution(CustomFunction customfunction, int z) {
        // 暴力
        List<List<Integer>> lists = new ArrayList<>();
        for(int i = 1; i <= 1000; i++){
            for(int j = 1; j <= 1000; j++){
                if(customfunction.f(i, j) == z){
                    lists.add(Arrays.asList(i, j));
                    break;
                }
            }
        }
        return lists;
    }
}

【代码二】通过—二分查找

/*
 * // This is the custom function interface.
 * // You should not implement it, or speculate about its implementation
 * class CustomFunction {
 *     // Returns f(x, y) for any given positive integers x and y.
 *     // Note that f(x, y) is increasing with respect to both x and y.
 *     // i.e. f(x, y) < f(x + 1, y), f(x, y) < f(x, y + 1)
 *     public int f(int x, int y);
 * };
 */
class Solution {
    public List<List<Integer>> findSolution(CustomFunction customfunction, int z) {
        List<List<Integer>> lists = new ArrayList<>();
        // 根据单调性,用双指针
        int l = 1;
        int r = 1000;
        while(l <= 1000 && r >= 1){ // 注意。。。
            int tmp = customfunction.f(l, r);
            if(tmp == z){
                lists.add(Arrays.asList(l, r));
                l++;
                r--;
            }else if(tmp < z){
                l++;
            }else{
                r--;
            }
        }
        return lists;
    }
}

【代码三】通过—双指针

/*
 * // This is the custom function interface.
 * // You should not implement it, or speculate about its implementation
 * class CustomFunction {
 *     // Returns f(x, y) for any given positive integers x and y.
 *     // Note that f(x, y) is increasing with respect to both x and y.
 *     // i.e. f(x, y) < f(x + 1, y), f(x, y) < f(x, y + 1)
 *     public int f(int x, int y);
 * };
 */
class Solution {
    public List<List<Integer>> findSolution(CustomFunction customfunction, int z) {
        List<List<Integer>> lists = new ArrayList<>();
        // 根据单调性,用双指针
        int l = 1;
        int r = 1000;
        while(l <= 1000 && r >= 1){ // 注意。。。
            int tmp = customfunction.f(l, r);
            if(tmp == z){
                lists.add(Arrays.asList(l, r));
                l++;
                r--;
            }else if(tmp < z){
                l++;
            }else{
                r--;
            }
        }
        return lists;
    }
}

162. 寻找峰值

在这里插入图片描述

【代码一】通过—二分查找

class Solution {
    public int findPeakElement(int[] nums) {
        if(nums == null || nums.length == 0){
            return -1;
        }
        int l = 0;
        int r = nums.length - 1;
        while(l <= r){
            if(l == r){
                return l;
            }
            if(l + 1 == r){
                return nums[l] > nums[r] ? l : r;
            }
            int mid = l + ((r-l)>>1);
            if(nums[mid] > nums[mid-1] && nums[mid] > nums[mid+1]){
                return mid;
            }else if(nums[mid] < nums[mid-1] && nums[mid] < nums[mid+1]){
                l = mid; // r = mid亦可
            }else if(nums[mid] > nums[mid-1] && nums[mid] < nums[mid+1]){
                l = mid;
            }else{
                r = mid;
            }
        }
        return -1;
    }
}

153. 寻找旋转排序数组中的最小值

在这里插入图片描述
【代码一】通过—二分查找
能够想到和最右侧元素比较,就太巧妙了。。。

class Solution {
    public int findMin(int[] nums) {
        // 考虑暴力情况,就是找第一个减小的值,没有第一个减小的值,那就是没有旋转,nums[0]就是所求。
        // 不管有没有旋转。都可以二分
        int l = 0;
        int r = nums.length - 1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(nums[mid] > nums[r]){ // 必定旋转了,且最小值在nums[mid]右侧
                l = mid + 1;
            }else if(nums[mid] < nums[r]){ // 不管旋转与否,最小值在nums[mid]左侧或者是nums[mid]
                r = mid;
            }else{ // 旋转情况最终也会变成一个单调递增情况,不断逼近,最终必定逼到r=mid
                return nums[mid];
            }
        } 
        return -1;
    }
}

【代码二】通过—暴力(寻找变小的元素)

class Solution {
    public int findMin(int[] nums) {
        for(int i = 1; i < nums.length; i++){
            if(nums[i-1] > nums[i]){ // 若旋转过,必定出现此种情形
                return nums[i];
            }
        }
        return nums[0]; // 没旋转过
    }
}

34. 在排序数组中查找元素的第一个和最后一个位置在这里插入图片描述

【代码一】通过—二分查找
想一想查找小于等于目标值的最小值的位置和大于等于目标值的最大值的位置,就迎刃而解了。

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int right = lower_bound(nums, target);
        int left = higher_bound(nums, target);
        return new int[]{left, right};
    }
    private int lower_bound(int[] a, int v){
        int l = 0;
        int r = a.length - 1;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] < v){
                l = mid + 1;
            }else if(a[mid] > v){
                r = mid - 1;
            }else{
                res = mid;
                l = mid + 1;
            }
        }
        return res;
    }
    private int higher_bound(int[] a, int v){
        int l = 0;
        int r = a.length - 1;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] > v){
                r = mid - 1;
            }else if(a[mid] < v){
                l = mid + 1;
            }else{
                res = mid;
                r = mid - 1;
            }
        }
        return res;
    }
}

29. 两数相除

在这里插入图片描述
【代码一】通过—暴力(思路的起点,减法代替除法)

class Solution {
    public int divide(int dividend, int divisor) {
        // (既然想到了减法代替除法)思路一步步升华:暴力---倍增
        // 注意1.避免溢出,都转成负数来运算
        // 注意2.处理溢出情形
        if(dividend == -2147483648 && divisor == -1){
            return 2147483647;
        }
        boolean sign = (dividend > 0) ^ (divisor > 0);
        if(dividend > 0){
            dividend = -dividend;
        }
        if(divisor > 0){
            divisor = -divisor;
        }
        int res = 0;
        while(dividend <= divisor){
            dividend -= divisor;
            res -= 1;
        }
        return sign ? res : -res;
    }
}

【代码二】通过—倍增(在暴力基础上加速,效果异常明显)
最优的当然是每次都减去最大的除数的倍数(二分),但这要借助乘法,不可行。

class Solution {
    public int divide(int dividend, int divisor) {
        // (既然想到了减法代替除法)思路一步步升华:暴力---倍增
        // 注意1.避免溢出,都转成负数来运算
        // 注意2.处理溢出情形
        if(dividend == -2147483648 && divisor == -1){
            return 2147483647;
        }
        boolean sign = (dividend > 0) ^ (divisor > 0);
        if(dividend > 0){
            dividend = -dividend;
        }
        if(divisor > 0){
            divisor = -divisor;
        }
        int res = 0;
        int tmp = divisor;
        int cnt = -1;
        while(dividend <= tmp){
            if(dividend <= divisor){
                dividend -= divisor;
                res += cnt;
                // divisor不断倍增,加速
                divisor += divisor;
                cnt += cnt;
            }else{
                divisor = tmp;
                cnt = -1;
            }
        }
        return sign ? res : -res;
    }
}

222. 完全二叉树的节点个数

在这里插入图片描述
【代码一】通过—暴力

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int countNodes(TreeNode root) {
        return root == null ? 0 : 1 + countNodes(root.left) + countNodes(root.right);
    }
}

【代码二】通过—二分查找(利用完全二叉树特性)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int countNodes(TreeNode root) {
        // 利用完全二叉树特性。先求高度h,确定前h-1层节点数,然后二分求第h层数
        // 可以根据题目的测试用例,分析出最终会少加1个节点,最终结果加1即可
        
        if(root == null){
            return 0;
        }
        
        int ans = 0; // 最终答案
        int h = 0; // 二叉树高度
        TreeNode tmp = root;
        while(tmp != null){
            h++;
            tmp = tmp.left;
        }
        ans += (1 << (h-1)) - 1; // 先加上前h-1层的节点数
        // 二分,需要用例子分析,才能理解为啥最后会少加1个节点
        while(--h > 0){
            TreeNode right = root.right;
            // 查看以right为根的子树是否在最后一层有节点贡献
            if(check(right, h)){
                // 有节点贡献的话,说明以root.left为根的子树最后一层是满的,全部加到ans上
                ans += (1 << (h-1));
                // 二分查找关键,转向右子树,减少一半搜索范围
                root = root.right;
            }else{
                // 二分查找关键,转向左子树,减少一半搜索范围
                root = root.left;
            }
        }
        return ++ans;
    }
    private boolean check(TreeNode root, int h){
        while(root != null){
            h--;
            root = root.left;
        }
        return h == 0;
    }
}

475. 供暖器

在这里插入图片描述在这里插入图片描述
【代码一】通过—二分查找

class Solution {
    public int findRadius(int[] houses, int[] heaters) {
        // 思路非常妙啊。。。查找每个房屋到最近加热器的距离,其中的最大值即为所求
        Arrays.sort(houses);
        Arrays.sort(heaters);
        int max = -1;
        for(int i = 0; i < houses.length; i++){
            max = Math.max(max, near(heaters, houses[i]));
        }
        return max;
    }
    private int near(int[] a, int v){
        int r = lower_bound(a, v);
        int l = upper_bound(a, v);
        if(l != - 1 && r != -1){
            return Math.min(v-a[r], a[l]-v);
        }
        int loc = r == -1 ? l : r;
        return Math.abs(a[loc]-v);
    }
    private int lower_bound(int[] a, int v){
        int l = 0;
        int r = a.length-1;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] <= v){
                res = mid;
                l = mid + 1;
            }else{
                r = mid - 1;
            }
        }
        return res;
    }
    private int upper_bound(int[] a, int v){
        int l = 0;
        int r = a.length-1;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] >= v){
                res = mid;
                r = mid - 1;
            }else{
                l = mid + 1;
            }
        }
        return res;
    }
}

1111. 有效括号的嵌套深度

在这里插入图片描述在这里插入图片描述在这里插入图片描述
【代码一】通过—技巧

class Solution {
    public int[] maxDepthAfterSplit(String seq) {
        /*
            对于某个括号,如果同为左括号或同为右括号,交替分配给A,B即可。
        */
        int len = seq.length();
        int[] res = new int[len];
        int flag = 0; // 0--->A, 1--->B
        for(int i = 0; i < len; i++){
            if(i != 0 && seq.charAt(i) == seq.charAt(i-1)){
                flag = 1 - flag; // 0,1交替
            }
            res[i] = flag;
        }
        return res;
    }
}

378. Kth Smallest Element in a Sorted Matrix

在这里插入图片描述
【代码一】通过—二分查找

class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int n = matrix.length;
        int l = matrix[0][0];
        int r = matrix[n-1][n-1];
        int res = 0;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(cnt(matrix, mid) < k){ // 关键
                l = mid + 1; // 答案必定在mid右侧
            }else{
                res = mid; // 根据题意res必定存在,记录res的可能值,二分完res就是答案
                r = mid - 1; // 答案必定就是mid或在mid左侧
            }
        }
        return res;
    }
    // 查找小于等于v的个数。从右上角到左下角利用单调性查找
    private int cnt(int[][] m, int v){
        int n = m.length;
        int i = 0;
        int j = n-1;
        int res = 0;
        while(i < n &&  j >= 0){
            if(m[i][j] <= v){
                res += j+1; // 第i行中的第j列及其左侧均小于等于v
                i++;
            }else{
                j--;
            }
        }
        return res;
    }
}

454. 四数相加 II

在这里插入图片描述
【代码一】通过—二分查找
主要解决的问题是避免超时,降到O(N^2)*logN

class Solution {
    public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
        int n = A.length;
        int[] e = new int[n*n];
        int[] f = new int[n*n];
        int cnt = 0;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                e[cnt] = A[i] + B[j];
                f[cnt] = C[i] + D[j];
                cnt++;
            }
        }
        Arrays.sort(e);
        Arrays.sort(f);
        int res = 0;
        for(int i = 0; i < n*n; i++){
            // 注意可能不止一个。。。
            int right = lower_bound(f, -e[i]);
            int left = upper_bound(f, -e[i]);
            if(left == -1 && right == -1){
                continue;
            }else if(left == -1 || right == -1){
                res++;
            }else{
                res += right - left + 1;
            }
        }
        return res;
    }
    private int lower_bound(int[] a, int v){
        int l = 0;
        int r = a.length-1;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] == v){
                res = mid;
                l = mid + 1;
            }else if(a[mid] < v){
                l = mid + 1;
            }else{
                r = mid - 1;
            }
        }
        return res;
    }
    private int upper_bound(int[] a, int v){
        int l = 0;
        int r = a.length-1;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] == v){
                res = mid;
                r = mid - 1;
            }else if(a[mid] < v){
                l = mid + 1;
            }else{
                r = mid - 1;
            }
        }
        return res;
    }
}

【代码二】通过—Map
主要解决的问题是避免超时,降到O(N^2)

class Solution {
    public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
        int n = A.length;
        int[] e = new int[n*n];
        int cnt = 0;
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                e[cnt] = A[i] + B[j];
                if(map.containsKey(e[cnt])){
                    map.put(e[cnt], map.get(e[cnt])+1);
                }else{
                    map.put(e[cnt], 1);
                }
                cnt++;
            }
        }
        int res = 0;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                if(map.containsKey(-C[i]-D[j])){
                    res += map.get(-C[i]-D[j]);
                }
            }
        }
        return res;
    }
}

718. 最长重复子数组

在这里插入图片描述
【代码一】超时—暴力
二分还是会超时的。。。leetcode分类错了吧。。。

class Solution {
    public int findLength(int[] A, int[] B) {
        // 暴力试一下。。。(果然超时:n^3)
        int max = 0;
        for(int i = 0; i < A.length; i++){
            for(int j = 0; j < B.length; j++){
                if(A[i] == B[j]){
                    int len = 1;
                    int p = i+1;
                    int q = j+1;
                    while(p < A.length && q < B.length){
                        if(A[p] == B[q]){
                            len++;
                        }else{
                            break;
                        }
                    }
                    max = Math.max(len, max);
                }
            }
        }
        return max;
    }
}

【代码二】通过—dp

class Solution {
    public int findLength(int[] A, int[] B) {
        int n1 = A.length;
        int n2 = B.length;
        int[][] dp = new int[n1+1][n2+1];
        int max = 0;
        for(int i = 1; i <= n1; i++){
            for(int j = 1; j <= n2; j++){
                if(A[i-1] == B[j-1]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                    max = Math.max(max, dp[i][j]);
                }
            }
        }
        return max;
    }
}

300. 最长上升子序列

在这里插入图片描述
【代码一】超时—暴力递归

class Solution {
    public int lengthOfLIS(int[] nums) {
        // 暴力查找,每个数要么选要么不选,O(2^n)
        if(nums == null || nums.length == 0){
            return 0;
        }
        return dfs(nums, -1, 0);
        
    }
    // 某个元素是否要选,取决于该元素与a[preIndex]对比。从curPos直至a.length进行dfs
    private int dfs(int[] a, int preIndex, int curPos){
        if(curPos == a.length){
            return 0;
        }
        int taken = 0;
        if(preIndex == -1 || a[curPos] > a[preIndex]){
            taken = 1 + dfs(a, curPos, curPos+1);
        }
        int notTaken = dfs(a, preIndex, curPos+1);
        return Math.max(taken, notTaken);
    }
}

【代码二】通过—记忆化搜索

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        int[][] dp = new int[nums.length][nums.length];
        for(int[] a : dp){
            Arrays.fill(a, -1);
        }
        return dfs(nums, -1, 0, dp);
        
    }
    // 记忆化搜索
    // 某个元素是否要选,取决于该元素与a[preIndex]对比。从curPos直至a.length进行dfs
    private int dfs(int[] a, int preIndex, int curPos, int[][] memo){
        if(curPos == a.length){
            return 0;
        }
        if(preIndex != -1 && memo[preIndex][curPos] != -1){
            return memo[preIndex][curPos];
        }
        int taken = 0;
        if(preIndex == -1 || a[curPos] > a[preIndex]){
            taken = 1 + dfs(a, curPos, curPos+1, memo);
        }
        int notTaken = dfs(a, preIndex, curPos+1, memo);
        int res = Math.max(taken, notTaken);
        if(preIndex != -1){
            memo[preIndex][curPos] = res;
        }
        return res;
    }
}

【代码三】通过—动态规划

// dp[i]表示必须以nums[i]结束的最长子序列的长度
class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int res = 1;
        for(int i = 1; i < n; i++){
            int max = 0;
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    max = Math.max(max, dp[j]);
                }
            }
            dp[i] = max + 1;
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

【代码四】通过—二分查找+维护一个动态严格递增数组

class Solution {
    public int lengthOfLIS(int[] nums) {
        // 二分查找+维护一个严格递增数组dp
        // 可以使用题目中的测试用例,用下面代码思考下,就知道流程了。
        if(nums == null || nums.length == 0){
            return 0;
        }
        int n = nums.length;
        int[] dp = new int[n];
        dp[0] = nums[0];
        int len = 0;
        for(int i = 1; i < n; i++){
            int loc = bi(dp, 0, len, nums[i]);
            if(loc == len){
                dp[++len] = nums[i];
            }else if(loc == -1){
                dp[0] = nums[i];
            }else{
                dp[loc+1] = nums[i];
            }
        }
        return len+1;
    }
    // 小于v的最右边的位置
    private int bi(int[] a, int st, int en, int v){
        int l = st;
        int r = en;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] < v){
                res = mid;
                l = mid + 1;
            }else{
                r = mid - 1;
            }
        }
        return res;
    }
}

1201. 丑数 III

在这里插入图片描述在这里插入图片描述
【代码一】通过—二分查找

class Solution {
    
    public int nthUglyNumber(int n, int a, int b, int c) {
        // 不管暴力还是二分。关键是怎么计算某个数是第几个丑数。。。
        // 丑数必定是a,b,c的整数倍,丑数的因子必定是a,b,c,分7种情况。
        // 1.只被a整除
        // 2.只被b整除
        // 3.只被c整除
        // 4.只被ab的最小公倍数lcmAB整除
        // 5.只被ac的最小公倍数lcmAC整除
        // 6.只被bc的最小公倍数lcmBC整除
        // 7.只被abc的最小公倍数lcmABC整除
        // 对某个数v来说,sum = v/a + v/b + v/c - (v/lcmAB+v/lcmAC+v/lcmBC) + lcmABC
        // 第sum个丑数就是v-min(v%a, v%b, v%c)
        long lcmAB = lcm(a, b);
        long lcmAC = lcm(a, c);
        long lcmBC = lcm(b, c);
        long lcmABC = lcm(lcm(a, b), c);
        long l = Math.min(a, Math.min(b, c));
        long r = l * n;
        while(l <= r){
            long mid = l + ((r-l)>>1);
            long sum = mid/a + mid/b + mid/c - mid/lcmAB - mid/lcmAC - mid/lcmBC + mid/lcmABC;
            if(sum == n){
                return (int)(mid - Math.min(mid%a, Math.min(mid%b, mid%c)));
            }else if(sum > n){
                r = mid - 1;
            }else{
                l = mid + 1;
            }
        }
        return -1;
    }
    private long gcd(long a, long b){
        return b == 0 ? a : gcd(b, a%b);
    }
    private long lcm(long a, long b){
        return a/gcd(a,b)*b;
    }
}

50. Pow(x, n)

在这里插入图片描述
【代码一】超时—暴力

class Solution {
    public double myPow(double x, int n) {
        long N = n;
        if(N < 0){
            x = 1/x;
            N = -N;
        }
        double res = 1.0;
        for(long i = 1; i <= N; i++){
            res *= x;
        }
        return res;
    }
}

【代码二】通过—二分(递归)

class Solution {
    public double myPow(double x, int n) {
        long N = n;
        if(N < 0){
            x = 1/x;
            N = -N;
        }
        return fastPow(x, n);
    }
    // 利用x^(2*n) = (x^n)^2,一次减少一半运算
    private double fastPow(double x, long n){
        if(n == 0){
            return 1.0;
        }
        double half = fastPow(x, n/2);
        if(n%2 == 0){
            return half *= half;
        }else{
            return half *= half*x;
        }
    }
}

【代码三】通过—二分(非递归)

class Solution {
    // 利用x^(2*n) = (x^n)^2,一次减少一半运算
    public double myPow(double x, int n) {
        long N = n;
        if(N < 0){
            x = 1/x;
            N = -N;
        }
        double res = 1.0;
        for(long i = N; i > 0; i /= 2){
            if(i % 2 != 0){
                res *= x;
            }
            x *= x;
        }
        return res;
    }
}

81. 搜索旋转排序数组 II

在这里插入图片描述
【代码一】通过—类二分查找

class Solution {

    public boolean search(int[] nums, int target) {
        // 分析。依旧是利用左边界或右边界,这道题更好地理解二分。。。
        // 本代码以利用左边界为例。利用右边界应该类似。
        // 最好画图理解,结合测试用例,结合单步调试。。。情况多导致细节较多
        if(nums == null || nums.length == 0){
            return false;
        }
        int l = 0;
        int r = nums.length - 1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(nums[mid] == target){
                return true;
            }
            if(nums[mid] > nums[l]){ // mid,l定位在左/右升序段
                if(nums[mid] <= nums[r]){
                    if(target > nums[mid]){
                        l = mid + 1;
                    }else{
                        r = mid - 1;
                    }
                }else{
                    if(target > nums[mid]){
                        l = mid + 1;
                    }else if(target <= nums[r]){
                        l = mid + 1;
                    }else if(target < nums[mid] && target >= nums[l]){
                        r = mid - 1;
                    }else{
                        return false;
                    }
                }
            }else if(nums[mid] < nums[l]){
                // nums[mid]在右升序段,nums[left]在左升序段
                if(target < nums[mid]){
                    r = mid - 1;
                }
                if(target > nums[mid] && target <=  nums[r]){
                    l = mid + 1;
                }
                if(target > nums[r]){
                    r = mid - 1;
                }
            }else{
                l = l + 1; // 左边界右移一位,这句话说明其实并非真正二分啊
            }
        }
        return false;
    }
}

911. 在线选举

在这里插入图片描述在这里插入图片描述
【代码一】通过—二分(关键是如何存储数据)
参考题解

class TopVotedCandidate {
    /*
        参考官方题解
    */
    
    /*
        第一个列表记录各个人的第一次得票
        第二个列表记录各个人的第二次得票
        以此类推。。。
    */
    private List<List<Node>> lists; 
    
    private class Node{
        public int p;
        public int t;
        public Node(int p, int t){
            this.p = p;
            this.t = t;
        }
    }
    
    public TopVotedCandidate(int[] persons, int[] times) {
        lists = new ArrayList<>();
        Map<Integer, Integer> map = new HashMap<>(); // 记录每个人出现的次数
        for(int i = 0; i < persons.length; i++){
            int p = persons[i];
            int t = times[i];
            int c = map.getOrDefault(p, 0) + 1;
            map.put(p, c);
            while(lists.size() < c){
                lists.add(new ArrayList<>());
            }
            lists.get(c-1).add(new Node(p, t));
        }
        
    }
    
    public int q(int t) {
        // 先找到小于等于t的最大的位置
        int l = 0;
        int r = lists.size() - 1;
        int loc = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(lists.get(mid).get(0).t <= t){
                loc = mid;
                l = mid + 1;
            }else{
                r = mid - 1;
            }
        }
        // 再在res这个列表种,查找最大的小于等于的位置,就是所求
        l = 0;
        r = lists.get(loc).size() - 1;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(lists.get(loc).get(mid).t <= t){
                res = mid;
                l = mid + 1;
            }else{
                r = mid - 1;
            }
        }
        return lists.get(loc).get(res).p;
    }
    
}

/**
 * Your TopVotedCandidate object will be instantiated and called as such:
 * TopVotedCandidate obj = new TopVotedCandidate(persons, times);
 * int param_1 = obj.q(t);
 */

497. 非重叠矩形中的随机点

在这里插入图片描述
【代码一】通过—巧妙的二分

class Solution {
    // 确实,刚开始就会直接随机选一个矩形。。。
    // 由于不同矩形贡献的点多少不同,所以应该使用矩形面积权重,结合二分左到均匀。。

    private int[][] rects;
    private Map<Integer, Integer> map;
    private int sum;
    private int[] help;
    
    public Solution(int[][] rects) {
        this.rects = rects;
        help = new int[rects.length];
        map = new HashMap<>();
        int sum = 0;
        for(int i = 0; i < rects.length; i++){
            map.put(sum, i);
            int a = rects[i][0];
            int b = rects[i][1];
            int c = rects[i][2];
            int d = rects[i][3];
            help[i] = sum;
            sum += (c-a+1)*(d-b+1);
        }
        this.sum = sum;
    }
    
    public int[] pick() {
        Random random = new Random();
        int randomR = random.nextInt(sum);
        int loc = bi(help, randomR); // 这个是关键
        int a = rects[loc][0];
        int b = rects[loc][1];
        int c = rects[loc][2];
        int d = rects[loc][3];
        int randomX = a + random.nextInt(c-a+1);
        int randomY = b + random.nextInt(d-b+1);
        return new int[]{randomX, randomY};
    }
    // 寻找小于等于v的最大值所在位置
    private int bi(int[] a, int v){
        int l = 0;
        int r = a.length - 1;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] <= v){
                res = mid;
                l = mid + 1;
            }else{
                r = mid -1;
            }
        }
        return res;
    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution obj = new Solution(rects);
 * int[] param_1 = obj.pick();
 */

209. 长度最小的子数组

在这里插入图片描述
【代码一】通过—暴力

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int len = Integer.MAX_VALUE;
        for(int i = 0; i < nums.length; i++){
            int sum = 0;
            for(int j = i; j < nums.length; j++){
                sum += nums[j];
                if(sum >= s){
                    len = Math.min(len, j - i + 1);
                    break;
                }
            }
        }
        return len == Integer.MAX_VALUE ? 0 : len;
    }
}

【代码二】通过—二分查找

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int len = Integer.MAX_VALUE;
        int[] help = new int[nums.length+1]; // help[i]记录前i-1项和
        for(int i = 1; i <= nums.length; i++){
            help[i] += help[i-1] + nums[i-1];
        }
        for(int i = 0; i < nums.length; i++){
            int loc = bi(help, s+help[i]);
            if(loc != -1){
                len = Math.min(loc-i, len);
            }
        }
        return len == Integer.MAX_VALUE ? 0 : len;
    }
    private int bi(int[] a, int v){
        int l = 0;
        int r = a.length - 1;
        int res = -1;
        while(l <= r){
            int mid = l + ((r-l)>>1);
            if(a[mid] >= v){
                r = mid - 1;
                res = mid;
            }else{
                l = mid + 1;
            }
        }
        return res;
    }
}

【代码三】通过—滑动窗口

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int len = Integer.MAX_VALUE;
        int left = 0;
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
            while(sum >= s){
                len = Math.min(len, i-left+1);
                sum -= nums[left];
                left++;
            }
        }
        return len == Integer.MAX_VALUE ? 0 : len;
    }
}

特别说明

本文参考leetcode官方网站题库及相关讨论区解答。链接地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值