备战秋招DAY4-今日力扣-二分查找

1.搜索插入位置(难度等级:简单)

这道题也算是二分法的经典题了,其实逻辑很简单,最关键的是边界条件的判断。 

class Solution {
    public int searchInsert(int[] nums, int target) {
        int start = 0;
        int end = nums.length-1;
        int res = nums.length;
        while(start<=end){
            int mid = ((end - start) >> 1) + start;
            if(nums[mid]>=target){
                res = mid;
                end = mid - 1;     
            }else{
                start = mid + 1;
            }
        }
        return res;
    }
}

2.搜索二维矩阵(难度等级:中等)

这道题其实就是上一道题的升级版,写法都是类似的~这里还有一个小技巧,将矩阵每一行拼接在上一行的末尾,则会得到一个升序数组,我们可以在该数组上二分找到目标元素。代码实现时,可以二分升序数组的下标,将其映射到原矩阵的行和列上。

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length, n = matrix[0].length;
        int low = 0, high = m * n - 1;
        while (low <= high) {
            int mid = (high - low) / 2 + low;
            int x = matrix[mid / n][mid % n];
            if (x < target) {
                low = mid + 1;
            } else if (x > target) {
                high = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
}

3. 在排序数组中查找元素的第一个和最后一个位置(难度等级:中等)

这道题我觉得沿着第一题思路就可以了。先找到数组中一个与target相等的元素,再在其左右寻找有没有其他相同元素,如果有及时更新结果即可~·

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int n=nums.length;
        int left=0,right=n-1;
        int mid=0;
        while(left<=right){
            mid=(left+right)/2;
            if(nums[mid]==target){
                int l=mid,r=mid;
                while(l>=0&&nums[l]==target) l--;
                while(r<n&&nums[r]==target) r++;
                return new int[]{l+1,r-1};
            }else if(nums[mid]<target){
                left=mid+1;
            }else{
                right=mid-1;
            }
        }
        return new int[]{-1,-1};
    }
}

4.搜索旋转排序数组(难度等级:中等)

这道题和第一题相比又增加了一个考虑维度,即数组不是有序的。这个时候我们还想用二分法,那么我们必须自己构造有序数组。看到一个非常好的解释:

  1. 将数组一分为二。(其中有一个一定是有序的;另一个则是有序或部分有序的)
  2. 此时如果target在有序部分里,则用二分法查找。
  3. 否则进入无序部分查找,返回1。
public int search(int[] nums, int target) {
        if(nums == null || nums.length == 0){
            return -1;
        }
        int start = 0;
        int end = nums.length - 1;

        while (start <= end){
            int mid = start + (end -start)/2;
            if (nums[mid] == target){
                return mid;
            }

            //后半部分有序
            if(nums[mid] < nums[end]){
                if(nums[mid] < target && target <= nums[end]){
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }

            } else {
                 if(nums[mid] > target && target >= nums[start]){
                    end = mid - 1;
                    
                } else {
                    start = mid + 1;
                    
                }


            }
        }
        return -1;
        
    }

也就是说,我们比1多判断一步,就是当前找的范围是否是有序的,并且永远只在有序的部分进行二分查找~

5.寻找旋转排序数组中的最小值(难度等级:中等)

这道题我认为就是寻找“断崖点”。仔细观察案例,你会发现旋转后的数组还是被分为2个有序数组,但是他们之间可能不是有序的,只要找到了这个点,就找到了答案。具体可以分为几种情况:

  • 左值 < 中值, 中值 < 右值 :没有旋转,最小值在最左边,可以收缩右边界
  • 左值 > 中值, 中值 < 右值 :有旋转,最小值在左半边,可以收缩右边界
  • 左值 < 中值, 中值 > 右值 :有旋转,最小值在右半边,可以收缩左边界
  • 左值 > 中值, 中值 > 右值 :单调递减,不可能出现
class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[right]) { //左半边有序,断点在右边         
                left = mid + 1;
            } else {                                
                right = mid; //右半边有序,断点在左边
            }
        }
        return nums[left];
    }
}

6.寻找两个正序数组的中位数(难度等级:困难)

本题最简单的思路就是将两个数组合并,然后求中位数。但是这种方法时间复杂度和空间复杂度都无法满足题目要求呀!这里不妨采用“排除”的思路,让我们来看这种情况:比较两个数组的第 k/2 个数字(如果 k 是奇数,向下取整),哪一个数组第k/2的数字更小,表明这个数组0~k/2的元素一定不是中位数了,这时候我们就可以将这些数排除!然后,我们反复进行如上相同的操作,不断排除数字,就能得到最终结果~

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int m = nums2.length;
        int left = (n + m + 1) / 2;
        int right = (n + m + 2) / 2;
        //将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。
        return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;  
}
    
    private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;
        //让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1 
        if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
        if (len1 == 0) return nums2[start2 + k - 1];

        if (k == 1) return Math.min(nums1[start1], nums2[start2]);

        int i = start1 + Math.min(len1, k / 2) - 1; //取当前K值下的第一个数组的中间元素索引
        int j = start2 + Math.min(len2, k / 2) - 1; //取当前K值下的第二个数组的中间元素索引

        if (nums1[i] > nums2[j]) {
            return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
        }
        else {
            return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
        }
    }
}

【总结】

二分法的使用场景往往是题目中出现 有序数组 的时候,其核心代码的逻辑其实就是1.中的逻辑,其他问题都是在1的基础上做了更多的考虑,只要掌握每个题巧妙的思路,就能解答了~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值