• java - 寻找一个值的二分查找、寻找左/侧边界的二分查找总结


    声明:总结基于labuladong总结的框架,感谢大佬

    1 以下搜索均为左闭右闭区间

    2 因此为了确保搜索不漏掉,while循环中为left <= right

     1、寻找一个值的二分查找

    1. public int search(int[] nums, int target) {
    2. int left = 0, right = nums.length - 1;
    3. while (left <= right) {
    4. int mid = left + (right - left) / 2;
    5. if (nums[mid] < target) {
    6. left = mid + 1;
    7. } else if (nums[mid] > target) {
    8. right = mid - 1;
    9. } else if (nums[mid] == target) {
    10. return mid;
    11. }
    12. }
    13. return -1;
    14. }

    寻找指定值的二分是最基本的二分搜索

    1 target找不到时,不断通过left = mid + 1或者right = mid - 1来缩小搜索区间

    2 一旦找到target就返回下标

    3 循环结束说明没找到target,返回-1即可

    2 、寻找左侧边界的二分查找

    1. public int searchLeft(int[] nums, int target) {
    2. int left = 0, right = nums.length - 1;
    3. while (left <= right) {
    4. int mid = left + (right - left) / 2;
    5. if (nums[mid] < target) {
    6. left = mid + 1;
    7. } else if (nums[mid] > target) {
    8. right = mid - 1;
    9. } else if (nums[mid] == target) {
    10. right = mid - 1;
    11. }
    12. }
    13. if (left == nums.length) return -1;
    14. return nums[left] == target ? left : -1;
    15. }

    寻找左侧边界的二分查找,即寻找多个符合target的最左侧的目标索引

    1 对比寻找指定目标值的题目,这种题目需求是当找到target时,右侧缩紧right = mid - 1,使得后续搜索区间均在每个目标值的左侧

    2 直到循环结束,此时left = right + 1,也就是说left已经移到了right右侧,同样mid = right + 1

    3 因为循环结束前最后一轮是left == right

    • 此时如果nums[mid] == target,right = mid - 1 = left - 1,但是返回的时候应该返回这个target的下标,即left的下标的,因此才有了最后的nums[left] == target ? left : -1
    • 此时如果nums[mid] != target,无论是大还是小,left = right + 1,然后最后nums[left] == target ? left : -1就会返回-1

    4 对于if (left == nums.length) return -1的原因是:当target > max(nums)时,left会一直右移,而right不会变。循环结束时,left = right + 1 = nums.length - 1 + 1 = nums.length,这时候返回-1

    3、寻找右侧边界的二分查找

    1. public int searchRight(int[] nums, int target) {
    2. int left = 0, right = nums.length - 1;
    3. while (left <= right) {
    4. int mid = left + (right - left) / 2;
    5. if (nums[mid] < target) {
    6. left = mid + 1;
    7. } else if (nums[mid] > target) {
    8. right = mid - 1;
    9. } else if (nums[mid] == target) {
    10. left = mid + 1;
    11. }
    12. }
    13. if (right == -1) return -1;
    14. return nums[right] == target ? right : -1;
    15. }

    寻找右侧边界的二分查找,即寻找多个符合target的最右侧的目标索引 

     1 对比寻找指定目标值的题目,这种题目需求是当找到target时,左侧缩紧left = mid + 1,使得后续搜索区间均在每个目标值的右侧

    2 直到循环结束,此时left = right + 1,也就是说left已经移到了right右侧,同样mid = right + 1

    3 因为循环结束前最后一轮是left == right

    • 此时如果nums[mid] == target,left = mid + 1 = right + 1,但是返回的时候应该返回这个target的下标,即right的下标,因此才有了最后的nums[right] == target ? right : -1
    • 此时如果nums[mid] != target,无论是大还是小,right = left - 1,然后最后nums[right] == target ? right : -1就会返回-1

    4 对于if (right == -1) return -1的原因是:当target < min(nums)时,right会一直左移,而left不会变。循环结束时,right = left - 1 = 0 - 1  = -1,这时候返回-1

     4、总结

    上述解释只有找个例子跑一遍才能理解,如果不想理解直接记住框架即可。在二分查找指定值的基础上,无论是寻找最左侧的指定值还是寻找最右侧的指定值,只需要记住下面几个原则:

    1 寻找左侧就把右侧收紧,即right= mid - 1,同样寻找右侧就要把左侧收紧,即left= mid + 1

    2 循环结束均需要判断一种极端情况,即target是远大于或者小于nums中的所有值的,这种情况导致:

    • 寻找左侧target时,target太大,导致左侧不断收紧,右侧不变,直到left == nums.length
    • 寻找右侧target时,target太小,导致右侧不断收紧,左侧不变,直到right == -1

    3  循环结束的时候,均为left = right + 1,但是左右侧查找导致处理不同:

    • 寻找左侧时,我们要的是最左侧的,主要是right在缩紧导致right跑到left左边,此时left有可能是目标值,所以返回前检查一下
    • 寻找右侧时,我们要的是最右侧的,主要是left在缩紧导致left跑到right右边,此时right有可能是目标值,所以返回前检查一下

    5、力扣实践
    34. 在排序数组中查找元素的第一个和最后一个位置https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/

    1. class Solution {
    2. public int[] searchRange(int[] nums, int target) {
    3. if (nums.length == 0) return new int[]{-1,-1};
    4. return new int[]{searchLeft(nums,target),searchRight(nums,target)};
    5. }
    6. public int searchLeft(int[] nums, int target){
    7. int left = 0,right = nums.length - 1;
    8. while (left <= right){
    9. int mid = left + (right - left) / 2;
    10. if (nums[mid] < target){
    11. left = mid + 1;
    12. }else if (nums[mid] > target){
    13. right = mid - 1;
    14. }else if (nums[mid] == target){
    15. right = mid - 1;
    16. }
    17. }
    18. if (left == nums.length) return -1;
    19. return nums[left] == target? left:-1;
    20. }
    21. public int searchRight(int[] nums, int target){
    22. int left = 0,right = nums.length - 1;
    23. while (left <= right){
    24. int mid = left + (right - left) / 2;
    25. if (nums[mid] < target){
    26. left = mid + 1;
    27. }else if (nums[mid] > target){
    28. right = mid - 1;
    29. }else if (nums[mid] == target){
    30. left = mid + 1;
    31. }
    32. }
    33. if (right == -1) return -1;
    34. return nums[right] == target? right:-1;
    35. }
    36. }

  • 相关阅读:
    [数据结构]单链表(从0->1)
    前端技能树,面试复习第 51 天—— Vue 项目性能优化方案
    java-net-php-python-ssm高校综合素质测评系统计算机毕业设计程序
    RocketMQ 消息存储机制分析
    大数据从入门到实战 --HDFS系统初体验
    网络知识TCP
    Android10及以上版本系统源码下载、编译、刷机、联网、问题解决的通用方案(Google Pixel3手机)
    浅析synchronized锁升级的原理与实现
    C++-Debug与Release的不同-Debug可行-Release下出错-莫名其妙的bug-AI插件开发
    vue3使用插件fullcalendar生成日历或工作台
  • 原文地址:https://blog.csdn.net/Gnikay/article/details/126902827