leetcode-hot100-二分查找

文章目录

  • [4. 寻找两个正序数组的中位数 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/median-of-two-sorted-arrays/)
  • [33. 搜索旋转排序数组 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/)
  • [34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)
  • [69. x 的平方根 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/sqrtx/)
  • [240. 搜索二维矩阵 II - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/search-a-2d-matrix-ii/)
  • [287. 寻找重复数 - 力扣(LeetCode) (leetcode-cn.com)](https://leetcode-cn.com/problems/find-the-duplicate-number/)

4. 寻找两个正序数组的中位数 - 力扣(LeetCode) (leetcode-cn.com)

详细题解:这道算法题太简单?你忽略了时间复杂度的要求! (qq.com)
代码详解:详细通俗的思路分析,多解法 - 寻找两个正序数组的中位数 - 力扣(LeetCode) (leetcode-cn.com)

  • 代码:
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;
    //总和技术,就是将同一位置求两边,不影响
    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) {
    	//k永远是第几小的数
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;
        //永远保证短的在上,长的在下
        if(len1 > len2){
            return getKth(nums2, start2, end2, nums1, start1, end1, k);
        }
       //只有长的,那么就找第k小的数,-1的目的是因为下标。
        if(len1 == 0){
            return nums2[start2 + k - 1];
        }
        //当递归到找最小的数的时候,此时就找两个数组起始位最小的,因为其他不符合条件的已经在前面递归进行了逻辑删除
        if(k == 1){
            return Math.min(nums1[start1], nums2[start2]);
        }
		// 分别找到第k/2大的数,min的目的是为了防止越界,-1的目的是找下标
        int i = start1 + Math.min(len1, k / 2) - 1;
        int j = start2 + Math.min(len2, k / 2) - 1;
		
        //当上面数组对应的值大于下面数组的时候,
        //那么下面数组到k/2位置的数都要删除,也就是(j - start2 + 1)
        //这里+1的目的是因为要删除[start2,j]这个区间的数值
        //删除之后,要对k值进行更新,即:k - 删除区间长度,
        //同样起始位置变成了j + 1,因为i,j表示数组的第k/2大的数。
        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));
        }
    }
}

33. 搜索旋转排序数组 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. 题目要求 O ( l o g n ) O(log_n) O(logn),所以进行二分,因为有确切值,所以使用精确查找。
  2. 本题因为是旋转过,所以要确定目标值在前半段还是在后半段。
  3. 如果mid对应的正好和target一致,那么就说明找到了。
  4. 没有和target一致,那么判断在哪个区间,如果在mid对应值大于等于num[0](因为前半段的区间起始永远是0下标),那么就在该区间进行判断,究竟是right还是left移动。
  5. 既不满足相等,也没有在前半段,那么只能是数组的后半段了,此时同样就是判断如何去移动left和right。
  6. 当程序走完还有返回,那么返回-1。
  • 代码:
class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        int left = 0, right = n - 1;
        //因为有目标值,所以是精确查找
        while(left <= right){
            int mid = left + ((right - left) >> 1);
            if(nums[mid] == target){
                return mid;
            }else if(nums[mid] >= nums[0]){
            //前半段,如果目标值大于起始位置,同时目标值小于中间位置,那么说明目标值在前半段的前半段。移动right
                if(target >= nums[0] && target < nums[mid]){
                    right = mid - 1;
                }else {
                //在前半段的后半段,移动left
                    left = mid + 1;
                }
            }else {
                if(target > nums[mid] && target <= nums[right]){
                    left = mid + 1;
                }else {
                    right = mid - 1;
                }
            }
        }
        return -1;
    }
}

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. 本题给出了目标值,但却是一个模糊查找,因为题目也没说具体是否存在该值。
  2. 设置两个函数,一个用来找第一个位置的,一个用来找最后一个位置的。
  3. 找第一个位置函数:
    1. 如果此时mid值正好等于给定目标值,那么应该将区间缩小而不是直接返回,因为要找的是第一个值出现的位置,此时的缩小策略就是将最右端放置在mid位置。
    2. 如果此时mid值小于目标值,说明[0,mid]这段区间的值都没有满足的,目标值必定在mid的右侧,那么移动left = mid + 1.
      3.如果此时mid值大于目标值,说明[mid,right]这个区间的值都大于目标值,目标值可能出现的范围是[0,mid],此时的mid所指的值也有可能是目标值,因为数字是可以重复的,所以将right移动到mid。
  4. 找最后一个位置函数:
    1. 在该函数中,求mid的时候,mid = left + ((right - left + 1) >> 1);否则按照查询条件,当只有两个值的时候,永远都只有一侧在进行值的比较,所以要+1.
    2. 如果此时mid所指向的值等于目标值的,因为是求最后一个位置,说明至少在mid处的值是等于target的,至于后面有不有再说,那么left置为mid,之后还可能出现相同的值。
    3. 如果此时mid所指向的值小于目标值,那么说明[0,mid)处的值是绝对小于target的,并且是找最后一个,指向mid位置的元素可能是最后一个,也可能不是最后一个,所以left要置为mid。
    4. 此时如果mid所指的值是大于目标值的,那么说明[mid,right]的值都是大于target的,所以将这段区域排除掉,right = mid - 1.

注:只需要在查找的过程中值是不等的,那么就要记得去缩减搜索空间。

  • 代码:
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int n = nums.length;
        if(n < 1){
            return new int[]{-1,-1};
        }
        int firstPosition = findFirstPosition(nums, target);
        if(firstPosition == -1){
            return new int[]{-1,-1};
        }
        int lastPosition = findLastPositon(nums, target);
        return new int[]{firstPosition, lastPosition};
    }

    private int findFirstPosition(int[] nums, int target){
        int n = nums.length;
        int left = 0, right = n - 1;
        while(left < right){
            int mid = left + ((right - left) >> 1);
            if(nums[mid] == target){
                //找到的可能不是第一个
                right = mid;
            }else if(nums[mid] < target){
                //目标值大于mid,因为找第一个位置,所以就移动left
                left = mid + 1;
            }else {
                //目标值小于mid,因为找第一个位置,所以将区间缩小,移动right
                right = mid;
            }
        }
        if(nums[left] == target){
            return left;
        }else {
            return -1;
        }
    }
    private int findLastPositon(int[] nums, int target){
        int n = nums.length;
        int left = 0, right = n - 1;
        while(left < right){
            int mid = left + ((right - left + 1) >> 1);
            if(nums[mid] == target){
                //找到的可能不是第一个
                left = mid;
            }else if(nums[mid] < target){
                //目标值大于mid,因为找最后一个位置,为了防止漏,那么将left先置为mid位置
                left = mid;
            }else {
                //目标值小于mid,因为找最后一个位置,因为此时mid值已经大于目标值了,所以最后一个位置肯定在mid前面
                right = mid - 1;
            }
        }
        return left;
    }
}

69. x 的平方根 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. 使用二分查找。
  2. 因为每个数必然会有一个平方根,那么使用精确查找。
  3. 首先判断给定的x是否为0或者1,是的话,就返回自己。
  4. left为0,right为x,使用变量res记录结果。
  5. 开始精确查找,如果(mid * mid <= x),此时就说明了left小了,并且此时mid很可能就会是答案,使用res保存起来,但是此时mid * mid可能数会很大,所以条件表达式应该是mid <= x / mid。如果大了,那么就移动right。
  6. 返回res。
    注:为什么要使用res去记录以及为什么mid<=x/mid的时候res就有可能会是答案?
    使用的是精确查找,那么当mid*mid>x的时候,说明此时不合适,right移动为mid-1,此时再看mid*mid与x的情况,如果小于等于x了,那么只是有可能为结果(并不一定),还需要再测一测mid至right这个区间的值,更合适的再进行更新。
  • 代码:
class Solution {
    public int mySqrt(int x) {
        if(x == 0 || x == 1){
            return x;
        }
        int left = 0, right = x;
        int res = 0;
        while(left <= right){
            int mid = left + ((right - left) >> 1);
            if(mid <= x / mid){
                res = mid;
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return res;
    }
}

240. 搜索二维矩阵 II - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. 本题其实不用二分,只需要在左下角和右上角选取一个就好。这里选择左下角。
  2. 如果相等就返回true。如果大于目标值,那么只需要上移就好,如果小于目标值,向右移动。(因为题目说了每行元素值向右依次递增,每列元素值向下依次递增)。
  • 代码:
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int n = matrix.length;
        int m = matrix[0].length;
        int i = n - 1, j = 0;
        while(i >= 0 && j < m){
            if(matrix[i][j] == target){
                return true;
            }else if(matrix[i][j] > target){
                i--;
            }else {
                j++;
            }
        }
        return false;
    }
}

287. 寻找重复数 - 力扣(LeetCode) (leetcode-cn.com)

  • 思路:
  1. 因为肯定存在一个重复值,所以使用精确查找,但其实也可以使用模糊查找。
    先说精确查找
    1. 二分查找都是用来查找顺序的,但给定的数组却不是有序的。如何变为有序是关键。
    2. 使用计数的方法,举例:以[1,6,2,3,4,5,2,7]为例,此时mid所指的数为4,那么整个数组没有重复的数的话,那么统计出≤4的个数应该最多只能有4个,但此时大于了4,那么说明重复数一定出现在1,2,3,4这四个中。
    3. 关于缩减:刚才举例的分析,如果此时count > mid,说明,当前重复数一定在(0,mid)之间,所以移动right,并且使用一个变量记录下mid,很可能mid就是那个重复的值。否则,说明重复数在(mid,right)之间,移动left。

模糊查找
1. 与精确查找不一样,模糊查找不需要额外的变量记录值,因为最后返回的就是left。
2. 区间缩减:当count > mid的时候,值在(0,mid)之间,但此时区间缩减却是right = mid,因为mid指向的值很可能就是答案,其实与精确查找分析一样;否则值就在(mid, right)之间,移动left就好。

举例:

nums 3,1,3,4,2
count 0,1,0,0,2
1,2,3,0,4

此时mid = 2,而count = 2 == mid,说明重复值在(mid,right)之间,移动left = mid + 1 = 3,而此时mid = 3,此时cnt = 4,用一个res记录此时的mid = 3,同时移动right = 2。

视频题解

  • 代码:
    使用精确查找:
class Solution {
    public int findDuplicate(int[] nums) {
        //因为说了只能使用常量级的额外空间,所以不能使用哈希
        //因为明确有重复数字,所以使用精确查找
        int n = nums.length;
        int left = 0, right = n - 1;
        int res = 0;
        while(left <= right){
            int mid = left + ((right - left) >> 1);
            int count = 0;//统计,不能定义在外面,
            //每次缩小区间的时候,要重新进行计算
            for(int i = 0; i < n; i++){
                if(nums[i] <= mid){
                    count++;
                }
            }
            if(count > mid){
                res = mid;
                right = mid - 1;
            }else {
                left = mid + 1;
            }
        }
        return res;
    }
}

使用模糊查找:
与上面精确查找的区别在于区间的缩减:

  1. 上面当count > mid的时候,使用res进行记录,同时right = mid - 1;反之,left = mid + 1。
  2. 这里当count > mid的时候,直接移动right = mid,反之移动left,至于原因,可以参考34题。
class Solution {
    public int findDuplicate(int[] nums) {
        int n = nums.length;
        int left = 0, right = n - 1;
        while(left < right){
            int mid = left + ((right - left) >> 1);
            int count = 0;//统计
            for(int i = 0; i < n; i++){
                if(nums[i] <= mid){
                    count++;
                }
            }
            if(count > mid){
                right = mid;
            }else {
                left = mid + 1;
            }
        }
        return left;
    }
}

你可能感兴趣的:(leetcode,leetcode,算法,职场和发展)