[leetcode刷题]——二分查找

  此篇博客主要记录力扣中二分查找的题。

  

一、求开方

69. x 的平方根

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

public int mySqrt(int x) {
        if(x <= 1) return x;
        int l = 0;
        int h = x;
        while(h >= l){
            int mid = (l + h) / 2;
            if(mid  > x /mid){  //如果使用 mid * mid > x ,则可能会内存溢出
                h = mid - 1;
            }else if(mid  < x / mid){
                l = mid + 1;
            }else{
                return mid;
            }
        }
        return h;
    }

 

二、大于给定元素的最小元素

744.寻找比目标字母大的最小字母  easy   2021-06-13

给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。

在比较时,字母是依序循环出现的。举个例子:

如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a'

 

  这个题考察的是二分,题目中是有序数组,我却使用了遍历。耗时一毫秒, 超过5%的用户。

public char nextGreatestLetter(char[] letters, char target) {
        int minLetter = 0;
        for(int i = 0; i < letters.length; i++){
            if(subLetter(letters[i], target) < subLetter(letters[minLetter], target)){
                minLetter = i;
            }
        }
        return letters[minLetter];
    }
    
    public int subLetter(char a, char b){
        if(a - b > 0){
            return a - b;
        }else{
            return a - b + 26;
        }
    }

 

  利用有序的性质,使用二分,耗时超过100%😊

public char nextGreatestLetter(char[] letters, char target) {
        int l = 0; 
        int h = letters.length - 1;
        while(l <= h){
            int m = l + (h - l) / 2;
            if(letters[m] <= target){
                l = m + 1;
            }else{
                h = m - 1;
            }
        }
        return l < letters.length ? letters[l] : letters[0];
    }

 

三、有序数组的Single Element

540.有序数组中的单一元素   medium   2021-06-13

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

public int singleNonDuplicate(int[] nums) {
        int l = 0;
        int h = nums.length - 1;
        while(l < h){
            int m = l + (h - l) / 2;
            if((m - l) % 2 == 0){ //两侧都是偶数个
                if(nums[m] == nums[m - 1]){ 
                    h = m - 2;
                }else if(nums[m] == nums[m + 1]){
                    l = m + 2;
                }else{
                    return nums[m];
                }
            }else{  //两侧都是奇数个
                if(nums[m] == nums[m - 1]){
                    l = m + 1;
                }else if(nums[m] == nums[m + 1]){
                    h = m - 1;
                }else{
                    return nums[m];
                }
            }
        }
        return nums[l];
    }

 

 

 

 四、第一个错误的版本

278. 第一个错误的版本   easy   2021-06-13

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数

public int firstBadVersion(int n) {
        int l = 1;
        int h = n;
        while(l < h){
            int m = l + (h - l) / 2;
            if(isBadVersion(m) == true){
                h = m - 1;
            }else{
                l = m + 1;
            }
        }
        if(isBadVersion(l) == true){
            return l;
        }else{
            return l + 1;
        }
    }

  我写的代码确实呆板,二分法需要仔细审题。标准解法更简洁

public int firstBadVersion(int n) {
    int l = 1, h = n;
    while (l < h) {
        int mid = l + (h - l) / 2;
        if (isBadVersion(mid)) {
            h = mid;
        } else {
            l = mid + 1;
        }
    }
    return l;
}

 

五、旋转数组的最小数字

153.寻找旋转排序数组中的最小值   medium  2021-06-13

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

  虽然程序运行正确,用时击败100%, 内存击败92%。但是总感觉程序不太优美。

public int findMin(int[] nums) {
        int len = nums.length;
        if(nums[0] <= nums[len - 1]){
            return nums[0];
        }
        int l = 0; 
        int h = len - 1;
        while(l < h){
            int m = l + (h - l) / 2;
            if(nums[m] > nums[l]){
                l = m;
            }else{
                h = m;
            }
        }
        return nums[h + 1];    
    }

  别人的做法,时间和空间消耗差不多,但是更简洁更清晰。

public int findMin(int[] nums) {
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int m = l + (h - l) / 2;
        if (nums[m] <= nums[h]) {
            h = m;
        } else {
            l = m + 1;
        }
    }
    return nums[l];
}

 

 六、查找区间

34. 在排序算法数组中查找元素的第一个和最后一个位置     medium   2021-06-13

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

  明知是个二分的题,但是却想不到二分的办法解决它。

public int[] searchRange(int[] nums, int target) {
        int[] res = {-1, -1};
        for(int i = 0; i < nums.length; i++){
            if(res[0] == -1 && nums[i] != target){
                continue;
            }else if(res[0] == -1 && nums[i] == target){
                res[0] = i;
                res[1] = i;
            }else if(res[0] != -1 && nums[i] == target){
                res[1] = i;
            }else{
                continue;
            }
        }
        return res;
    }

 

  正确的的做法,可惜不是我想出来的😭

public int[] searchRange(int[] nums, int target) {
        int start = findFirst(nums, target);
        int end = findFirst(nums, target + 1) - 1; 
        if(start == nums.length || nums[start] != target){
            return new int[]{-1, -1};
        }else{
            return new int[]{start, end};
        }
    }
    
    public int findFirst(int[] nums, int target){
        int l = 0;
        int h = nums.length;  //这里并不是nums.length - 1。 因为我们需要更大的范围去找end.
        while(l < h){
            int m = l + (h - l) / 2;
            if(nums[m] >= target){
                h = m;
            }else{
                l = m + 1;
            }
        }
        return h;
    }

 

 

 

posted @ 2021-06-13 17:56  -野比大雄-  阅读(125)  评论(0)    收藏  举报