[Leetcode] Binary Search(69, 374, 33, 278, 162, 153, 34, 658, 270, 702, 50, 367, 349, 287, 4)

Binary Search

class Solution {
    public int search(int[] nums, int target) {
        if(nums.length == 0) return -1;
        int l = 0, r = nums.length -1;
        while(l <= r) {
            int mid = (l + r) / 2;
            if(nums[mid] == target) {
                return mid;
            } else if(nums[mid] < target) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return -1;
    }
}

TC: O(logn)

  1. Sqrt(x)
    link: https://leetcode.com/problems/sqrtx/

Solution: binary search

class Solution {
    public int mySqrt(int x) {
        if(x < 2) return x;
        int l = 2, r = x/2;
        int mid;
        while(l <= r) {
            mid = (l + r) / 2;
            if(Math.pow(mid, 2) == x) {
                return mid;
            } else if(Math.pow(mid, 2) < x) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return r;
    }
}

TC: O(logn)
Note: 1. 当x小于2时,返回x本身;2. 最后返回右边值的原因是,最终值的平方必然小于x,则在while体内left总是会在最后+1,因此只有right是正确的。

  1. Guess Number Higher or Lower
    link: https://leetcode.com/problems/guess-number-higher-or-lower/

Solution 1: 二分

public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int l = 1, r = n;
        int p1;
        while(l <= r) {
            p1 = l + (r - l) / 2;
            int res = guess(p1);
            if(res == 0) {
                return p1;
            } else if (res < 0) {
                r = p1 - 1;
            } else {
                l = p1 + 1;
            }
        }
        return -1;
    }
}

Solution 2: 三分

public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int l = 1, r = n;
        int p1, p2;
        while(l <= r) {
            p1 = l + (r - l) / 3;
            p2 = r - (r - l) / 3;
            int r1 = guess(p1);
            int r2 = guess(p2);
            if(r1 == 0) {
                return p1;
            } 
            if(r2 == 0){
                return p2;
            } else if(r1 == -1) {
                r = p1 - 1;
            } else if (r2 == 1) {
                l = p2 + 1;
            } else {
                l = p1 + 1;
                r = p2 - 1;
            }
        }
        return -1;
    }
}

(left + right) / 3, (left + right) * 2 / 3

left + (right - left) / 3, right - (right - left) / 3
前者会产生越界:程序首先计算left + right的值,和会越界。

  1. Search in Rotated Sorted Array
    link: https://leetcode.com/problems/search-in-rotated-sorted-array/

Solution:

class Solution {
    int[] nums;
    int target;
    // 先二分找到pivot,再从pivot的左/右二分查找
    public int findPivot(int left, int right) {
        
        if(nums[left] < nums[right]) {
            return 0;
        }
        
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[mid + 1]) {
                return mid + 1;
            } else {
                if(nums[mid] < nums[left]) { // 此处如果用nums[mid] > nums[left]判断则会出错,举例[8,9,1,2,3]
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
        }
        return 0;
    }
    
    public int helper(int left, int right) {
        int res;
        while(left <= right) {
            res = left + (right - left) / 2;
            if(nums[res] > target) {
                right = res - 1;
            } else if(nums[res] < target) { 
                left = res + 1;
            } else {
                return res;
            }
        }
        return -1;
    }
    
    public int search(int[] nums, int target) {
        if(nums.length == 0) return -1;
        if(nums.length == 1) return nums[0] == target ? 0 : -1;
        
        this.nums = nums;
        this.target = target;
        int len = nums.length;
        
        // 二分找到pivot
        int p = findPivot(0, len - 1);
        // nums[p]是全数列的最小值
        
        
        // 如果target等于nums[p],则在[0:p], 比最末位小在[p+1:],
        // target is the smallest number
        if(target == nums[p]) {
            return p;
        }
        
        // nums is not rotated
        if(p == 0) 
            return helper(0, len - 1);
        if(target < nums[0]) // 如果用target > nums[0]判断会出现问题,corner case[3, 1]
            return helper(p, len - 1);
        return helper(0, p);
    }
}
  1. First Bad Version
    link: https://leetcode.com/problems/first-bad-version/

Solution:

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        if(n == 0) return 0;
        if(n == 1) return isBadVersion(1) == true ? 1 : 0;
        
        int left = 1, right = n;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(isBadVersion(mid) == false) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        
        if(isBadVersion(left) == true)
            return left;
        return 0;
    }
}
  1. Find Peak Element
    link: https://leetcode.com/problems/find-peak-element/

Solution 1:

class Solution {
    public int findPeakElement(int[] nums) {
        if(nums.length == 0) return 0;
        // if(nums.length == 1) return 0;
        
        int left = 0, right = nums.length - 1;
        while(left < right) {
            int mid = left + (right - left) / 2;      
            if(nums[mid] < nums[mid + 1]) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
}

Solution 2:
不同的二分方法

class Solution {
    public int findPeakElement(int[] nums) {
        if(nums == null || nums.length == 0) return 0;
        if(nums.length == 1) return 0;
        if(nums.length == 2) return nums[0] > nums[1] ? 0 : 1;
        
        int left = 0, right = nums.length - 1;
        while(left + 1 < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] < nums[mid + 1]) {
                left = mid;
            } else {
                right = mid;
            }
        }
        return nums[right] > nums[left] ? right : left;
    }
}

Note:

  1. 要注意右边位置的选择,究竟是len还是len - 1
  2. mid的值总是靠左,因此mid - 1会越界,而mid + 1不会

153 Find Minimum in Rotated Sorted Array
link: https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/

Solution:

class Solution {
    public int findMin(int[] nums) {
        int len = nums.length;
        if(len == 0) return 0;
        if(len == 1) return nums[0];
        
        int l = 0, r = len - 1;
        if(nums[l] < nums[r])
                return nums[l];
        while(l < r) {
            int mid = l + (r - l) / 2;
            if(nums[mid] < nums[0])
                r = mid;
            else
                l = mid + 1;
        }
        return nums[r];
    }
}

Note: 注意中间值的判断和返回值的选择

  1. Find K Closest Elements
    link: https://leetcode.com/problems/find-k-closest-elements/

Solution:
如果目标值小于等于最小或者大于等于最大,则取数组两端的k个值;
目标值在数组中间,那么进行二分搜索,找到目标值的索引,或者距离目标值最近的数字的索引;
取索引前后的 k - 1 个值作为区间,一共2k - 1个值,最后结果一定在这个区间内。此处需要注意区间边界,左边不能小于0,右边不能大于数组长度;
循环比较两端距离x的远近,渐渐缩小区间直到区间长度为k。

class Solution {
    public int binarySearch(int[] arr, int target) {
        int l = 0, r = arr.length - 1;
        while(l <= r) {
            int mid = l + (r - l) / 2;
            if(arr[mid] == target) {
                return mid;
            } else if(arr[mid] > target) {
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        // 比较哪边的差值更小,取更小的那个
        return arr[l] - target < target - arr[r] ? l : r;
    }
    
    
    public List<Integer> findClosestElements(int[] arr, int k, int x) {
        List<Integer> res = new ArrayList<>();
        for(int i=0; i<arr.length; i++) {
            res.add(arr[i]);
        }
        // x小于等于最小或大于等于最大,直接取两端k个值
        if(x <= arr[0]) {
            return res.subList(0, k);
        } else if(x >= arr[arr.length - 1]) {
            return res.subList(arr.length - k, arr.length);
        } else {
            // 首先找到k,或者是距离k最近的值
            int idx = binarySearch(arr, x);
            int leftIdx = Math.max(idx - k + 1, 0);
            int rightIdx = Math.min(idx + k - 1, arr.length - 1);
            while(rightIdx - leftIdx + 1 != k) {
                int leftDiff = x - arr[leftIdx];
                int rightDiff = arr[rightIdx] - x;
                if(leftDiff > rightDiff) {
                    leftIdx++;
                } else {
                    rightIdx--;
                }
            }
            return res.subList(leftIdx, rightIdx+1);
        }
    }
}
  1. Closest Binary Search Tree Value
    link: https://leetcode.com/problems/closest-binary-search-tree-value/

Solution:

class Solution {
    public int closestValue(TreeNode root, double target) {
        TreeNode res = root;
        double diffMin = Double.MAX_VALUE;
        while(root != null) {
            double diffRo = root.val - target;
            if(Math.abs(diffRo) < Math.abs(diffMin)) {
                diffMin = diffRo;
                res = root;
            }
            
            if(diffRo > 0) {
                root = root.left;
            } else if(diffRo < 0) {
                root = root.right;
            } else {
                break;
            }
        }
        return res.val;
    }
}
  1. Search in a Sorted Array of Unknown Size
    link: https://leetcode.com/problems/search-in-a-sorted-array-of-unknown-size/

Solution:
判断右边是否越界,如果越界,则二分
如果不越界,可能出现过度二分的情况,检查值的大小
如果小于target,说明已经过度二分,left设为当前值,同时right保留上一个值
如果大于target,则进行正常二分

class Solution {
    public int search(ArrayReader reader, int target) {
        if(reader.get(0) == 2147483647) return -1;
        int left = 0, right = 19999;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(reader.get(mid) == 2147483647) {
                right = mid;
            } else {
                if(reader.get(mid) < target) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
        }
        return reader.get(right) == target ? right : -1;
    }
}
  1. Pow(x, n)
    link: https://leetcode.com/problems/powx-n/

Solution:
迭代
第三行不可以用 if(n == 1) return x
corner case:当n = -2 ^ 31时,会越界

class Solution {
    public double helper(double x, int n) {
        if(n == 0) return 1.0;
        double temp = helper(x, n/2);
        if(n % 2 == 0) {
            return temp * temp;
        } else {
            return temp * temp * x;
        }
    }
    
    public double myPow(double x, int n) {
        if(n == 0) return 1.0;
        if(x == 0) return 0.0;
        double res;
        if(n > 0) {
            res = helper(x, n);
        } else {
            res = helper(1/x, -n);
        }
        return res;
        // return n > 0 ? res : 1/res;
    }
}
  1. Valid Perfect Square
    link: https://leetcode.com/problems/valid-perfect-square/

Solution1: 简单二分
需要注意计算平方时可能会产生越界

class Solution {
    public boolean isPerfectSquare(int num) {
        if(num == 0 || num == 1) return true;
        int l = 1, r = num / 2;
        while(l < r) {
            int m = l + (r - l) / 2;
            if((long)m * m == (long)num) {
                return true;
            } else if((long)m * m < (long)num) {
                l = m + 1;
            } else {
                r = m - 1;
            }
        }
        if(l * l == num) return true;
        else return false;
    }
}

Solution2: 牛顿法
具体推算方法见:https://blog.csdn.net/chenrenxiang/article/details/78286599

class Solution {
    public boolean isPerfectSquare(int num) {
        if(num == 0 || num == 1) return true;
        long x = num / 2;
        while(x * x > num) {
            x = (x + num / x) / 2;
        }
        return (x * x == num);
    }
}
  1. Find Smallest Letter Greater Than Target
    Solution:
class Solution {
    public char nextGreatestLetter(char[] letters, char target) {
        int len = letters.length;
        if(target - 'a' >= letters[len - 1] - 'a' || target - 'a' < letters[0] - 'a')
            return letters[0];
        int left = 0, right = len - 1;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(letters[mid] - 'a' > target - 'a') {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return letters[right];
    }
}
  1. Find Minimum in Rotated Sorted Array II
    link: https://leetcode.com/problems/find-minimum-in-rotated-sorted-array-ii/

Solution:
注意在遇到重复值的时候对边界的处理方式
不可以和nums[0]进行比较

class Solution {
    public int findMin(int[] nums) {
        if(nums.length == 1) return nums[0];
        int len = nums.length;
        // 没有rotate
        if(nums[0] < nums[len - 1]) return nums[0];
        
        int left = 0, right = len - 1;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[right]) {
                left = mid + 1;
            } else if (nums[mid] < nums[right]){
                right = mid;
            } else {
                right--;
            }
        }
        return nums[right];
    }
}

Worst case TC: O(N)

  1. Intersection of Two Arrays
    link: https://leetcode.com/problems/intersection-of-two-arrays/
    Solution1:
    使用两个set并求交集
class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        HashSet<Integer> set1 = new HashSet<>();
        HashSet<Integer> set2 = new HashSet<>();
        List<Integer> res = new ArrayList<>();
        if(nums1.length == 0 || nums2.length == 0)
            return new int[0];
        
        for(int i=0; i<nums1.length; i++) {
            set1.add(nums1[i]);
        }
        
        for(int i=0; i<nums2.length; i++) {
            set2.add(nums2[i]);
        }
        
        for(Integer s : set1) {
            if(set2.contains(s)) {
                res.add(s);
            }
        }
        
        int[] res1 = new int[res.size()];
        for(int i=0; i<res.size(); i++) {
            res1[i] = res.get(i);
        }
        return res1;
    }
}

Solution2: sort以后二分查找

class Solution {
    public boolean binarySearch(int x, int[] nums) {
        int l = 0, r = nums.length - 1;
        while(l <= r) {
            int mid = l + (r - l) / 2;
            if(nums[mid] == x) {
                return true;
            } else if(nums[mid] < x) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return false;
    }
    
    public int[] intersection(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int len1 = nums1.length, len2 = nums2.length;
        List<Integer> res = new ArrayList<>();
        if(len1 == 0 || len2 == 0 || nums1[0] > nums2[len2-1] || nums1[len1-1] < nums2[0]) {
            return new int[0];
        }
        int l = 0, r = 1;
        while(r < len1) {
            if(nums1[l] == nums1[r]) {
                r++;
            } else {
                if(binarySearch(nums1[l], nums2)) {
                    res.add(nums1[l]);
                }
                l = r;
                r++;
            }
        }
        
        if(binarySearch(nums1[l], nums2)) {
            res.add(nums1[l]);
        }
        
        int[] res1 = new int[res.size()];
        for(int i=0; i<res.size(); i++) {
            res1[i] = res.get(i);
        }
        return res1;
    }
}

TC:sort: O(nlogn), 遍历数组O(n),二分O(logn)

Solution3: Sort以后双箭头线性查找

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        if(nums1.length == 0 || nums2.length == 0) return new int[0];
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int idx = 0, p1 = 0, p2 = 0;
        while(p1 < nums1.length && p2 < nums2.length) {
            if(nums1[p1] == nums2[p2]) {
                if(idx == 0) {
                    nums1[idx] = nums1[p1];
                    idx++;
                } else {
                    if(nums1[p1] != nums1[idx - 1]) {
                         nums1[idx] = nums1[p1];
                         idx++;
                    }
                }
                p1++;
                p2++;
            } else if(nums1[p1] < nums2[p2]) {
                p1++;
            } else {
                p2++;
            }
        return Arrays.copyOf(nums1, idx);
    }
}

TC: 线性查找O(m+n)

Note: 在同样是sort的情况下,如果m,n相差较大,则使用二分更快,如果m,n较为接近,则线性查找更快。
m = 2, n = 100 : 102 > 2log100
m = 100, n = 100: 200 < 100log100

  1. Find the Duplicate Number
    link: https://leetcode.com/problems/find-the-duplicate-number/

Solution: 佛洛依德算法(龟兔赛跑),此算法常用于判断linkedlist是否存在环
算法过程:

  1. 兔子以乌龟的两倍速前进,直到两者相遇
  2. 乌龟回到原点,兔子速度降到与乌龟同速,两者再次相遇的点即为重复的数字/环的起点

推导过程:
假设经过F step到达环的起点,环周长为C step,
第一次相遇,乌龟走过了F + a,兔子走过了F + nC + a,因为兔子的速度是乌龟的两倍,由此推出:
2(F + a) = (F + nC + a)
化简得到:
F + a = nC
第二次相遇,乌龟回到起点,经过F step到达环的起点
兔子在第一次相遇的位置,即F+a(即在环起点后的a个位置),经过F step,兔子到达a+F的位置,又因为F = nC - a,所以兔子其实到达了nC的位置,即环的起点

class Solution {
    public int findDuplicate(int[] nums) {
        int t = nums[0], h = nums[nums[0]];
        while(t != h) {
            t = nums[t];
            h = nums[nums[h]];
        }
        
        t = 0;
        while(t != h) {
            t = nums[t];
            h = nums[h];
        }
        
        return h;
    }
}
  1. Median of Two Sorted Arrays
    link: https://leetcode.com/problems/median-of-two-sorted-arrays/

数组长度分别为n1和n2,中位数的位置 k 为:
奇数个数字:(n1+n2+1) / 2
偶数个数字:(n1+n2+1) / 2为左中位数
从两个数组中分别取出m和n个数,使得 m+n = k
需要满足的条件是 A[m - 1] < B[n] && A[m] > B[n-1]
左中位数为A[m - 1],B[n-1]中较大的一个
右中位数为A[m], B[n]中较小的一个
对一个数组进行二分求出m

Solution:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;
        if(n1 > n2) return findMedianSortedArrays(nums2, nums1);
        
        // n1 + n2 is odd: k就是解
        // n1 + n2 is even: k是左中位数
        int k = (n1 + n2 + 1) / 2;
        int m, n;
        int left = 0, right = n1; // 重要:此处的right表示的是数组的长度
        while(left <= right) {
            m = left + (right - left) / 2;
            n = k - m;
            if (m > 0 && n < n2 && nums1[m-1] > nums2[n]) {
                right = m - 1;
            } else if (m < n1 && n > 0 && nums1[m] < nums2[n-1]) {
                left = m + 1;
            } else {
                int maxLeft;
                if(m == 0) {
                    maxLeft = nums2[n-1];
                } else if(n == 0) {
                    maxLeft = nums1[m-1];
                } else {
                    maxLeft = Math.max(nums1[m-1], nums2[n-1]);
                }
                if((n1 + n2) % 2 == 1) { 
                    return maxLeft;
                } else {
                    int minRight;
                    if(m == n1) {
                        minRight = nums2[n];
                    } else if(n == n2) {
                        minRight = nums1[m];
                    } else {
                        minRight = Math.min(nums1[m], nums2[n]);
                    }
                    return (maxLeft + minRight) / 2.0;
                }
            } 
        }
        return -1;
    }
}

Note:

  1. 对较短的数组进行二分,会提高计算速度

  2. lc的解法比花花的解法好在,如果第一次就已经找到了正确的中位数位置,则可以直接进入最终计算,少了一个二分查找的时间

  3. 本题需要特别注意各种边界条件,比如right是数组的长度而非index,当数组触碰到边界(小于0或者大于长度)时的处理。

  4. Find K-th Smallest Pair Distance
    link: https://leetcode.com/problems/find-k-th-smallest-pair-distance/

Solution:
首先找到差的区间,对此区间进行二分
将数组排序,二分取到中点差值diff,对于第 j 位的数字,计算nums[j] - nums[0 … j-1]的差并与diff比较,记录小于diff的差 / pairs的个数。
如果个数大于k,则中点diff取值过大,反之则过小。

class Solution {
    public int smallestDistancePair(int[] nums, int k) {
        Arrays.sort(nums);
        int low = 0, high = nums[nums.length - 1] - nums[0];
        int count = 0;
        while(low <= high) {
            int mid = low + (high - low) / 2;
            int j = 0;
            for(int i=0; i < nums.length; i++) {
                while(j < nums.length && nums[j] - nums[i] <= mid) j++;
                count += j - i - 1;
                if(count < k) {low = mid + 1;}
                else if(count >= k) {high = mid - 1;}
            }
        }
        return low;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值