二分查找(普通+左边界+有边界)

二分查找(普通+左边界+有边界)

具体详解来自力扣:
https://leetcode-cn.com/problems/binary-search/solution/er-fen-cha-zhao-xiang-jie-by-labuladong/

特别鸣谢labuladong dalao的详解!!!

下面是自己修改的二分模板,觉得需要记住

普通二分

也就是怎么找都可以,对应力扣第704题,链接:https://leetcode-cn.com/problems/binary-search/

public int search(int[] nums, int target) {
		if(nums.length==0)	return -1;
        int left = 0, 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 if(nums[mid]>target)    right=mid-1;
        }
        return -1;
    }

左边界二分:

    public int search(int[] nums, int target) {
    	if(nums.length==0)	return -1;
        int left=0, right=nums.length;//注意,右边界超出
        while(left<right){  //左闭右开
            int mid = left+(right-left)/2; //取中位数,这么做防止相加过大溢出
            if(nums[mid]==target)   right=mid;      //找到后不返回,将左边界缩小
            else if(nums[mid]>target) right=mid;    //因为是左闭右开,所以right不能等于mid-1
            else if(nums[mid]<target) left=mid+1;   //而left必须mid+1
        }
        if(left==nums.length)  return -1;
        return nums[left]==target?left:-1; //因为有可能找不到所以这个需要判断再返回
    }

右边界二分:

    public int search(int[] nums, int target) {
    	if(nums.length==0)  return -1;
        int left=0, 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;   //而left必须mid+1
            else if(nums[mid]>target) right=mid;    //因为是左闭右开,所以right不能等于mid-1
        }
        if(left==0)  -1;//防止left=0
        return nums[left-1]==target?left-1:-1; //因为有可能找不到所以这个需要判断再返回,并且因为是左闭右开,所以left要减一
    }

记忆法:
1、普通二分,左闭右闭,left=0,right=nums.length-1,while(left<=right),找到立刻返回,找不到返回-1;
2、边界二分,左闭右开,left=0,right=nums.length,while(left<right);
3、边界二分时,else if 时 nums[mid]和target,哪边大就往哪里推,比如左边(nums[mid])大于右边(target),就left=mid+1(往右推,改left),相反就right=mid(往左推,当然如果只是普通二分,就right=mid-1)
4、边界二分时可能存在找不到的情况,那就在返回时加上判断,结果等于就返回,不能等就返回-1。。。注意:右边界二分时,是判断left-1

通用二分模板

秒杀所有模板,不过变量定义有点多
原题:x的平方根

class Solution {
public:
    int mySqrt(int x) {
        // 原始边界
        ll el = 0, er = x;
        // 遍历用的左右边界
        ll l = 0, r = x;
        // 用中间变量pre_l和pre_r来防止死循环,剩下的靠业务需求来修改,把更多心思放在业务处理上而不是担心死循环
        for(int pre_l=l, pre_r=r; l<=r; pre_l=l, pre_r=r){
            ll mid = l+(r-l)/2;
            // 按照业务需求来改
            ll pow = mid*mid;
            if(pow==x)  return mid;
            else if(pow>x)  r = mid - 1;
            else if(pow<x)  l = mid;
            // 如果l和r跟原来一样,那就break
            if(pre_l==l && pre_r==r)    break;
        }
        // 按照业务需求来改
        // 检查l和r是否超边界,并且是否达到题目的要求,是就返回
        if(el<=r && r<=er && r*r<=x)   return r;
        if(el<=l && l<=er && l*l<=x)   return l;
        // 毛都没有,返回-1
        return -1;
    }
};

例题

1、在有序数组(升序)中查找第一个大于等于target的数,且数组中没有重复的数,若是数组中不存在这个数,则返回数组长度
例题:最长上升子序列,力扣300题。

public int fun(int[] arr, int target){
        int l=0, r=arr.length;
        while(l<r){
            int mid = l+(r-l)/2;
            if(arr[mid]==target)   {//找到一个等于自己的数,就字节返回了
            	l=mid;
            	break;
            }
            else if(arr[mid]>target)   r=mid;
            else if(arr[mid]<target)   l=mid+1;
        }
        return l;
    }

相反,查找数组中最后一个小于target的数,就是上面的代码结果-1.

变式:在有序数组(降序)中查找最后一个大于target的数

对应力扣下一个排列
其实就是将上面的反转过来,需要注意的是找中点时因为整数取商是向下取整的,这里需要在结果加一,代码模板

    private int bina(int[] nums, int l, int target){
        int r = nums.length-1;
        while(l<r){
            int mid = l+(r-l)/2+1;  //需要在这里加一
            if(nums[mid]<=target)  r = mid-1;
            else if(nums[mid]>target) l=mid;
        }
        return r;
    }

剑指 Offer 11. 旋转数组的最小数字

class Solution {
    public int minArray(int[] numbers) {
        int left = 0, right = numbers.length-1;
        if(numbers[left]<numbers[right])    return numbers[left];
        while(left+1<right){
            if(numbers[left]==numbers[right]) {
                int min = numbers[left];
                for(int i=left+1; i<right; i++) min = min>numbers[i]?numbers[i]:min;
                return min;
            }
            int mid = left + (right - left) / 2;
            System.out.println(mid);
            if(numbers[mid]>numbers[right]) left = mid;
            else if(numbers[mid]<=numbers[right]) right = mid;
        }
        return numbers[right];
    }
}

变式:假设按照升序排序的数组在预先未知的某个点上进行了旋转

就是用上面的查找最小的数值的下标后,向左右两边的数组进行查找:

class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        if(n==0)    return -1;
        if(nums[0]>target&&target>nums[n-1])    return -1;
        int minIndex = min(nums);
        // System.out.println(minIndex);
        int l = erfen(nums, minIndex, n-1, target);
        int r = erfen(nums, 0, minIndex-1, target);
        return l!=-1?l:r;
    }
    public int min(int[] nums){
        int l=0, r=nums.length-1;
        if(nums[l]<nums[r]) return l;
        while(l+1<r){
            if(nums[l]==nums[r]){
                int []min = new int[]{l,nums[l]};
                for(int i=l; i<=r; i++) 
                    if(min[1]<nums[i]){
                        min[0] = i;
                        min[1] = nums[i];
                    }
                return min[0];
            }
            int mid = l+(r-l)/2;
            if(nums[mid]>nums[r])   l=mid;
            else r=mid;
        }
        return r;
    }
    public int erfen(int[] nums, int l, int r, int target){
        if(l>r) return -1;
        while(l<=r){
            int mid = l+(r-l)/2;
            if(nums[mid]==target)   return mid;
            else if(nums[mid]>target)   r=mid-1;
            else if(nums[mid]<target)   l=mid+1;
        }
        return -1;
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值