二分查找算法

 力扣704 二分查找

public int search(int[] nums, int target) {
        int left=0;
        int right=nums.length-1;//左闭右闭区间
        while(left<=right){//问题
            int mid=(left+right)/2;
            if(nums[mid]<target) left=mid+1;
            else if(nums[mid]>target) right=mid-1;
            else return mid;
        }
        return -1;
    }

问题:为什么是while(left<=right)而不是while(left<right)?

前提:int left=0;int right=nums.length-1;表示取值范围是左闭右闭区间。

因为while语句作为终止循环的条件之一,当nums数组中没有target时,while停止循环就是当区间为空时。

如果是while(left<=right):说明终止条件是left=right+1,区间为[right+1,right],相当于[3,2],因此循环停止。

如果是while(left<right):说明终止条件是left=right,区间为[right,right],相当于[2,2],这时循环停止,但是这时区间不为空,index=2处的数值就被漏掉了。

剑指offer 53 在排序数组中查找数字

思路:寻找target的右边界

public int search(int[] nums, int target){
        int left=0;
        int right=nums.length-1;
        int mid=0;
        while(left<=right){
            mid=(left+right)/2;
            if(nums[mid]<=target) left=mid+1;
            else right=mid-1;
        }
        int l=left;//为什么右边界是left?
        if(right>=0&&nums[right]!=target){
            return 0;
        }
        left=0;
        right=nums.length-1;
        while(left<=right){
            mid=(left+right)/2;
            if(nums[mid]>=target) right=mid-1;
            else left=mid+1;
        }
        int r=right;
        return l-r-1;
    }

问题:

为什么右边界是left?

因为if(nums[mid]<=target) left=mid+1; 使得循环不停的在找到了的target的右侧找下一个target,直到找到了最后一个target,这时候同样满足if(nums[mid]<=target),所以left=mid+1;使得left指向target的下一个数,数组是升序的,后面再也不会有满足nums[mid]<=target条件的数了,因此left直到循环结束都保持不变,因此循环结束后left就是target的右边界。

为什么right指向最后一个target?

当left已经指向target右边界后,一直保持不变,right从右往左移动,最终导致循环结束,也就是区间为空,这时候right+1=left,所以right指向target。

优化代码:

public int search(int[] nums,int target) {
        return helper(nums,target)-helper(nums,target-1);
    }

    public int helper(int[] nums, int target) {
        int left=0;
        int right=nums.length-1;
        while(left<=right){
            int mid=(left+right)/2;
            if(nums[mid]<=target) left=mid+1;
            else right=mid-1;
        }
        return left;
    }

力扣69 x的平方根

思路:类似剑指offer 53 在排序数组中查找数字

如果这个整数的平方 恰好等于 输入整数,那么我们就找到了这个整数;
如果这个整数的平方 严格大于 输入整数,那么这个整数肯定不是我们要找的那个数;
如果这个整数的平方 严格小于 输入整数,那么这个整数 可能 是我们要找的那个数(重点理解这句话)。

因此采用二分查找算法找到平方最接近x的最大值,这和上道题找到target最右边界的思路类似。不同在于if(mid<=x/mid) left=mid;下一次查找的区间变为[mid,right],因为mid可能就是我们要找的数。

public int mySqrt(int x){
        if(x<=1) return x;
        int left=0;
        int right=x/2;
        while(left<right){
            int mid=left+(right-left+1)/2;
            if(mid>x/mid) right=mid-1;
            else left=mid;
        }
        return left;
    }

为什么最后返回的是left?

因为if(mid<=x/mid) left=mid; 使得循环不停的在找到了的mid的右侧找下一个mid(右侧包括mid),直到找到了最后一个mid,这时候同样满足if(mid<=x/mid) ,所以left=mid;使得left指向我们要求的结果。又因为数组是升序的,后面再也不会有满足mid<=x/mid条件的数了,因此left直到循环结束都保持不变,left就是要求的平方根。

为什么while(left<right)没有=?

while(left<right)表示最后循环结束的条件是left=right,区间为[left,left],最后循环结束的时候区间不为空,index=left处的数值看似被漏掉了,实际上index=left已经在if(mid<=x/mid) left=mid; 这句时计算过了,所以并没有被漏掉。

int mid=left+(right-left+1)/2;为什么要+1?

因为if(mid<=x/mid) left=mid;这句使得left最后固定下来之后,在最后区间只有两个数的时候,两者取的平均数mid会一直等于left,造成程序死循环,因此改变取平均数的方法,向上取整。

力扣35 搜索插入位置

思路:和以上题目类似,只不过本题是遍历数组找出大于等于target的数中下标最小的,因此if(nums[mid]>=target) right=mid;在[left,mid]中找大于等于target的数。

如果这个数小于target,一定不是要求的位置;

如果这个数等于target,一定是要求的位置;

如果这个数大于target,可能是要求的位置。

因此,nums[mid]>=target时 right=mid。(不是mid+1是因为这个数有可能是最终结果,不能立马排除在外)

while语句没有等于,是因为当循环结束的时候,left=right,而right的值都是mid赋给的,每个mid都受到过判断,所以当循环结束后,并没有漏掉任何情况。

public int searchInsert(int[] nums, int target) {
        if(nums[nums.length-1]<target) return nums.length;
        int left=0;
        int right=nums.length-1;
        while(left<right){
            int mid=(left+right)/2;
            if(nums[mid]<target) left=mid+1;
            else right=mid;
        }
        return left;
    }

剑指offer11 旋转数组的最小数字

思路:是要找最小的数字,运用二分查找,有以下几种情况:

​​​​​​​ 

public int minArray(int[] numbers){
        int left=0;
        int right=numbers.length-1;
        while(left<right){
            int mid=(left+right)/2;
            if(numbers[mid]<numbers[right]) right=mid;
            else if(numbers[mid]>numbers[right]) left=mid+1;
            else right=right-1;
        }
        return numbers[left];
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值