二分查找-Java【算法学习day.1】

3 篇文章 0 订阅
3 篇文章 0 订阅

一.概念 

        二分查找法,它充分利用了元素间的次序关系,采用分治策略,可在最坏的情况下用O(log n)完成搜索任务。它的基本思想是:(这里假设数组元素呈升序排列)将n个元素分成个数大致相同的两半,取a[n/2]与欲查找的x作比较,如果x=a[n/2]则找到x,算法终止;如 果x<a[n/2],则我们只要在数组a的左半部继续搜索x;如果x>a[n/2],则我们只要在数组a的右 半部继续搜索x。


二.在题目中掌握思想(代码中会有注释)

ps:题目均选自力扣

1.二分查找

题目链接704. 二分查找 - 力扣(LeetCode)

题面:

基本分析:可以通过遍历一遍查找target,这样时间复杂度是O(n),而二分是O(logn),而在二分的基础使用中应该注重的就是边界问题,常见的是左闭右开和左闭右闭,什么时候l<=r,什么时候l<r,什么时候l=m+1等等。下面会有两版代码分别讲解左闭右开和左闭右闭

左闭右闭

class Solution {
    public int search(int[] nums, int target) {
        int l = 0;//定义左边界
        //定义右边界,此时我们是以nums.length-1为右边界,
        //那么我们是认为右边界是有意义的,请牢记这一点
        int r = nums.length-1;
        int flag = -1;//用来标记目标的索引
        while(l<=r){//因为右边界是有意义的,所以i可以等于j
            int m = (l+r)>>>1;//相当于(i+j)/2,在java中整数间的除法是向下取整
            //nums[m]>target,则索引m右边的元素都大于target,由于m不可能是target且j有意义,所以j=m-1
            if(nums[m]>target)r = m-1;
             //nums[m]<target,则索引m左边的元素都小于target,由于m不可能是target且i有意义,所以i=m+1
            else if(nums[m]<target) l=m+1;
            //相等,则是目标元素,标记并跳出循环
            else {
                flag = m;
               break;
            };
        }
        return flag;
    }
}

左闭右开:

class Solution {
    public int search(int[] nums, int target) {
        int l = 0;//定义左边界
        //定义右边界,此时我们是以nums.length为右边界,
        //由于右边界超出了数组索引范围,那么我们是认为右边界是没有意义的,请牢记这一点
        int r = nums.length;
        int flag = -1;//用来标记目标的索引
        while(l<r){//因为右边界是没有意义的,所以i不等于j
            int m = (l+r)>>>1;//相当于(i+j)/2,在java中整数间的除法是向下取整
            //nums[m]>target,则索引m右边的元素都大于target,由于m不可能是target且j没有意义,所以j=m
            if(nums[m]>target)r = m;
             //nums[m]<target,则索引m左边的元素都小于target,由于m不可能是target且i有意义,所以i=m+1
            else if(nums[m]<target) l=m+1;
            //相等,则是目标元素,标记并跳出循环
            else {
                flag = m;
               break;
            };
        }
        return flag;
    }
}

现在你应该对二分有了一个基本的认识了


2.搜索插入位置

题目链接:35. 搜索插入位置 - 力扣(LeetCode)

题面:

基本分析:从排序数组和时间复杂度不难看出,这道题需要我们使用二分。首先,如果目标值存在于数组,那么思想就和第一题一样,如果目标不存在呢?我们想一下,假如我们采用左闭右闭区间,循环的条件是l<=r,那么循环破除的条件便是l>r,当循环来到最后一步,也就是进入循环时l=r,此时m也等于l和r,这里破除循环分两种情况:

1.r=m-1导致l>r,跳出循环

由于我们的判断条件可知,m索引右边的元素始终大于目标元素,m索引左边的元素始终小于目标元素,最后r=m-1,也就是m索引所指向的元素大于目标元素,而m-1所指向的元素肯定是小于目标元素,题目需要我们返回插入被按顺序插入的位置,也就是第一个比目标元素大的位置,也就是m,也就是l。

2.l=m+1导致l>r,跳出循环

也就是m索引所指向的元素小于目标元素,而m+1所指向的元素肯定是大于目标元素,题目需要我们返回插入被按顺序插入的位置,也就是第一个比目标元素大的位置,也就是m+1,由于l刚好被加一了,所以就是l。

综上所述,如果目标元素存在于数组内,我们返回标记值,如果不存在,返回l。

代码:

class Solution {
    public int searchInsert(int[] nums, int target) {
         int l = 0;
         int r = nums.length-1;
         int flag = -1;
         while(l<=r){
            int m = (l+r)>>>1;
            if(nums[m]>target)r=m-1;
            else if (nums[m]<target)l=m+1;
            else {
                flag = m;
                break;
            }
         }
         return flag==-1?l:flag;
    }
}

3.在排序数组中查找元素的第一个和最后一个位置

题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

题面:

基本分析: 在左闭右闭中,如果我把判断条件改为nums[m]>=target,r=m-1亦或者nums[m]<=target,l=m+1会发生什么变化?

我们就拿nums[m]>=target,r=m-1来讲述,则循环条件中代码如下:

  while(l<=r){
        int m = (l+r)>>>1;
        if(nums[m]>=target)r=m-1;
        else l = m+1;
     } 

上述代码:我们假设循环来到了最后一步,也就是l=r=m,目标元素(可以是多个)存在于数组内我们分为两种情况:

        a.是因为l=m+1导致循环跳出

        如果走的是第一个条件,也就是nums[m]>=target,由于目标值存在于数组内,什么情况下不走第一个?也就是上一步中nums[m]==target,然后走了第一个条件了。那我们就可以推断出m+1就是目标元素最左边的所以,由于l+1了,所以就是l;

        b.是因为r=m-1导致循环跳出

        说明走的是第一个条件,由于是最后一步,不可能存在m索引左边存在元素大于等于target,因为此时i都等于m了,也就是说,最后一步是nums[m]==target,m就是目标元素最左边的位置,所以也就是l;

思想知道了,代码奉上:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] arr2 = { -1, -1 };
        if (nums.length == 0)
            return arr2;
        int i1 = 0, i2 = 0;
        int j1 = nums.length - 1, j2 = nums.length - 1;
        while (i1 <= j1) {
            int m = (i1 + j1) >>> 1;
            if (nums[m] >= target)
                j1 = m - 1;
            else
                i1 = m + 1;
        }
        if (!(i1 <= j2 && nums[i1] == target))
            return arr2;
        arr2[0] = i1;
        while (i2 <= j2) {
            int m = (i2 + j2) >>> 1;
            if (nums[m] <= target)
                i2 = m + 1;
            else
                j2 = m - 1;
        }
        arr2[1] = j2;
        return arr2;
    }
}

三.后言

上面是二分查找一些最经典的题目,以后碰到其他好题也会更新,希望有所帮助,一同进步,共勉!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值