二分查找法

二分查找法

1、二分查找基本模板

int binarySearch(int[] nums, int target) {
    int left = 0, right = ...;

    while(...) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            ...
        } else if (nums[mid] < target) {
            left = ...
        } else if (nums[mid] > target) {
            right = ...
        }
    }
    return ...;
}

2、while 循环的条件中是 <=,还是 <?

1. 如果right=nums.length - 1 ,则是<=,
   对应的后面: left=mid+1;
              right=mid-1;
2. 如果right=nums.length ,则是<
   对应后面:left=mid+1;
            right=mid;

3、三种类型代码对比(都是用right=nums.length-1)

//最基本的二分查找算法
int binary_search(int[] nums, int target) {
    int left = 0, right = nums.length - 1; 
    while(left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1; 
        } else if(nums[mid] == target) {
            // 直接返回
            return mid;
        }
    }
    // 直接返回
    return -1;
}
//寻找左侧边界的二分查找:(第一个大于等于target的下标)
int left_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 别返回,锁定左侧边界
            right = mid - 1;
        }
    }
    // 最后要检查 left 越界的情况
    if (left >= nums.length || nums[left] != target)
        return -1;
    return left;
}

//寻找右侧边界的二分查找(第一个大于target下标-1):
/*注意如果需要查看第一个大于target小标则检查越界的时候不需要判断越界
 if (nums[right] != target)
        return right;
else
     return right+1;
 */
            
int right_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if (nums[mid] == target) {
            // 别返回,锁定右侧边界
            left = mid + 1;
        }
    }
    // 最后要检查 right 越界的情况
    if (right < 0 || nums[right] != target)
        return -1;
    return right;
}

//需要一个目标值的区间左右边界
class Solution {
    public int[] searchRange(int[] nums, int target) {
    int leftIndex=binarySearch(nums,target,true);
    int rightIndex=binarySearch(nums,target,false);
    if(leftIndex==-1||rightIndex == -1){
         return new int[]{-1,-1};
    }
     return new int[]{leftIndex, rightIndex};
    }
    
    public int binarySearch(int[] nums, int target, boolean flag)
    {
         int left = 0;
         int right = nums.length - 1;
         while (left<=right){
             int mid =left +(right -left)/2;
             if(nums[mid] > target){
                 right = mid -1;
             }else if(nums[mid] < target){
                 left = mid + 1;
             }else if(nums[mid] == target){
                 if(flag ==true){
                     right =mid -1;
                 }else{
                     left =mid + 1;
                 }
             }
         }
        //为true为左边界,检查左边界left是否越界
       if(flag == true){
           if(left>nums.length-1 || nums[left]!= target){
               return -1;
           }else{
               return left;
           }
       }else{//为false为有边界,检查右边边界right是否越界
            if(right<0 || nums[right]!= target){
               return -1;
           }else{
               return right;
           }
       }
    }
}

4、二分法查找一个区间的数值、查找第一个大于某个元素的位置

例如[1,4]:(0-1]返回下标0,
(1,4].则返回下标1

 private int binarySearch(int x) {
        int low = 0, high = pre.length - 1;
        while (low < high) {
            int mid = (high - low) / 2 + low;
            if (pre[mid] < x) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return low;
    }

5、有序数组旋转后的二分查找

将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。

这启示我们可以在常规二分查找的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:

在这里插入图片描述

class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        if (n == 0) {
            return -1;
        }
        if (n == 1) {
            return nums[0] == target ? 0 : -1;
        }
        int l = 0, r = n - 1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            if (nums[0] <= nums[mid]) {//和num[0]比较,从而得知哪边是有序的
        //一定要加等号,因为mid是向下取整的,所以可能会和nums[0]重合相等,
       // 这种情况下,当右边只有一个值就会出现错误(对于只有两个数的数组的情况会出现错误
                if (nums[0] <= target && target < nums[mid]) {
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            } else {
                if (nums[mid] < target && target <= nums[n - 1]) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
        }
        return -1;
    }
}

3、峰值问题,数值不重复其实等价于求最大值

class Solution {
    //虽然数组无序,但是可以通过查找最大值得概念来使用二分查找
    public int findPeakElement(int[] nums) {
        int left = 0;
        int right = nums.length-1;
        while(left<=right){
            int mid = left +(right-left)/2;
           if(compare(nums,mid,mid+1)>0 && compare(nums,mid-1,mid)<0){
               return mid;
           }else if(compare(nums,mid,mid+1)<0){
               left=mid+1;
           }else if(compare(nums,mid-1,mid)>0){
              right=mid-1;
           }   
        }
        return -1;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值