Leetcode分类刷算法之二分查找专题

Leetcode上必做的二分查找题目

摘自秋招之路:个人历程以及面经总结

二分查找,有序数组直接考虑二分查找,如旋转有序数组的最小值、旋转有序数组查找目标值、有序数组目标值的最左和最右位置、二分查找寻找峰值,二分查找问题,要考虑while(left <= right)是否可以等于,right = mid还是right = mid +1,返回值是left还是right,反正多考虑考虑边界问题。

1.在排序数组中查找元素的第一个和最后一个位置

34.在排序数组中查找元素的第一个和最后一个位置

二分查找算法细节详解

寻找左边界:
因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid + 1 和 right = mid

因为我们需找到 target 的最左侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧右侧边界以锁定左侧边界

寻找右边界:
因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid + 1 和 right = mid

因为我们需找到 target 的最右侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧左侧边界以锁定右侧边界

又因为收紧左侧边界时必须 left = mid + 1
所以最后无论返回 left 还是 right,必须减一


 public int[] searchRange(int[] nums, int target) {
        int left = getLeftBound(nums, target);
        int right = getRightBound(nums, target);
        return new int[]{left, right};
    }

    private int getLeftBound(int[] nums, int target) {
        int left = 0;
        int right = nums.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                right = mid;
            }else if (nums[mid] < target) {
                left = mid + 1;
            }else {
                right = mid;
            }
        }
        if (left == nums.length) {
            return -1;
        }
        return nums[left] == target ? left : - 1;
    }


    private int getRightBound(int[] nums, int target) {
        int left = 0;
        int right = nums.length ;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                left = mid + 1;
            }else if (nums[mid] < target) {
                left = mid + 1;
            }else {
                right = mid ;
            }
        }
        if (left == 0)return -1;
        return nums[left - 1] == target ? left - 1 : -1;
    }

2. 搜索插入位置

35. 搜索插入位置

 public int searchInsert(int[] nums, int target) {
        if (nums == null || nums.length < 0)return -1;
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left +  (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            }else if (nums[mid] < target) {
                left = mid + 1;
            }else {
                right = mid - 1;
            }
        }
         //left = right + 1 时候退出循环
        return left;
    }

3. 搜索旋转排序数组

33. 搜索旋转排序数组
题目要求 O(logN)O(logN) 的时间复杂度,基本可以断定本题是需要使用二分查找,怎么分是关键。
由于题目说数字了无重复,举个例子:
1 2 3 4 5 6 7 可以大致分为两类,
第一类 2 3 4 5 6 7 1 这种,也就是 nums[start] <= nums[mid]。此例子中就是 2 <= 5。
这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。
第二类 6 7 1 2 3 4 5 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2。
这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end],则在后半部分找,否则去前半部分找。

public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) { 
                return mid;
            }
            if (nums[left] <= nums[mid]) { //前半部分有序
                if (target >= nums[left] && target < nums[mid]) {
                    right = mid - 1;
                }else {
                    left = mid + 1;
                }
            }else { //后半部分有序
                if (target > nums[mid] && target <= nums[right]) { 
                    left = mid + 1;
                }else {
                    right =mid - 1;
                }
            }
        }
        return -1;
    }
}

4. 81. 搜索旋转排序数组 II

81. 搜索旋转排序数组

https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/
本题是需要使用二分查找,怎么分是关键,举个例子:

第一类
1011110111 和 1110111101 这种。此种情况下 nums[start] == nums[mid],分不清到底是前面有序还是后面有序,此时 start++ 即可。相当于去掉一个重复的干扰项。
第二类
22 33 44 55 66 77 11 这种,也就是 nums[start] < nums[mid]。此例子中就是 2 < 5;
这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。
第三类
66 77 11 22 33 44 55 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2;
这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end]。则在后半部分找,否则去前半部分找。

public boolean search(int[] nums, int target) {
        if (nums == null || nums.length == 0){
            return false;
        }
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) { 
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return true;
            }
            if (nums[mid] == nums[left]) { //分不清前面有序 还是后面有序 left++ 取出一个重复项
                left++;
                continue;
            }
            if (nums[left] < nums[mid]) {
                if(nums[left] <= target && target < nums[mid]) {
                    right = mid - 1;
                }else {
                    left = mid + 1;
                }
            }else{
                if (nums[mid] < target && target <= nums[right]) {
                    left = mid + 1;
                }else {
                    right = mid - 1;
                }
            }
        }
        return false;
    }

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

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

设置left,right左右边界,算出中间数nums[mid]
当nums[mid] > nums[right]时,说明出现了无序的地方在右边
left = mid+1
否则无序点在左侧
right = mid
两边夹逼直到left == right ,剩下的一个元素即为无序点

找最小值,一定存在不需要 left<= right,二分法寻找可能不存在的值才需要取等号。
  • while(left < right) 在循环体外输出
    
  • while(left <= right) 在循环体内输出
    
public int findMin(int[] nums) {
       int left = 0;
       int right = nums.length - 1;
       while (left < right) { //[left, right)
           int mid = left + (right - left) / 2;
           if (nums[mid] > nums[right]) {
               left = mid + 1; 
           }else {
               right = mid;
           }
       }
       return nums[left]; //左右夹逼,缩小区间到只剩一个元素
    }

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

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

寻找旋转排序数组中的最小值 II(二分法,极简,图解)

public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = (right - left) / 2 + left;
            if (nums[mid] == nums[right]) {
                right--;
                continue;
            }
            if (nums[mid] > nums[right]) {
                left = mid + 1;
            }else {
                right = mid;
            }
        }
        return nums[left];
    }

7. 搜索二维矩阵 II

240. 搜索二维矩阵 II

取数组左上角的元素

public boolean searchMatrix(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0)return false;
        int row = 0;
        int col = matrix[0].length - 1;
        while (row < matrix.length && col >= 0) {
            if (matrix[row][col] ==  target) {
                return true;
            }else if (matrix[row][col] < target) {
                row++;
            }else {
                col--;
            }
        }
        return false;
    }

8. 4. 寻找两个有序数组的中位数

4. 寻找两个有序数组的中位数

9. H指数 II

275. H指数 II
先复习一下 274. H指数

求图中正方形的最大边长

public int hIndex(int[] citations) {
        Arrays.sort(citations);
        reverse(citations);
        for (int i = 0; i < citations.length; i++) {
            if (i + 1 == citations[i]) return i + 1;
            if (i + 1 > citations[i])return i;
        }
        return citations.length;
    }

    private static void reverse(int[] citations) {
        int left = 0;
        int right = citations.length - 1;
        while (left < right) {
            int temp = citations[left];
            citations[left] = citations[right];
            citations[right] = temp;
            left++;
            right--;
        }
    }

H指数 II
数组已经按照升序排列

  1. 直接把上面解法中的排序去掉,代码复制过去就可以AC
  2. 看 nums[mid] 和 [mid, len - 1]的长度 即 len - 1 + mid + 1 = len - mid的长度
  3. 要返回的是nums中的值
  4. [0, 1, 2, 5, 6]
public int hIndex(int[] citations) {
        int len = citations.length;
        if (len == 0 || citations[len - 1] == 0)return 0;
        int left = 0;
        int right = citations.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (citations[mid] < len - mid) { //[0 , 1, 2 ,5 ,6] 2 <  (5 - 2 = 3)比长度小 就应该去掉该值
                left = mid + 1;
            }else {
                //比长度大是满足的,我们应该继续让mid王座去尝试看看有没有更小的mid的值
                //可以满足 mid对应的值大于等于从[mid, len - 1]的长度 [0, 1, 2, 3, 5, 6]
                right = mid; 
            }
        }
        return len - left;
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值