【基础算法】二分查找算法

前言:判断一道题是否能用二分,可以根据这道题是否有二段性来区分

简单模板

通过题目向大家讲解二分算法

链接: https://leetcode.cn/problems/binary-search/description/

在这里插入图片描述
这是一个很简单的二分查找的题目。那么接下来我们就通过题目来理解二分算法。

题意很简单:在一个升序的数组中查找目标值target,如果找到target就返回该值的下标,找不到就返回-1。

常规的做法:常规做法就是遍历一遍数组,如果遇到值为target的数,返回其下标,否则在遍历完之后返回-1 时间复杂度O(n)

不过常规做法没有考虑到数组的特性
升序

更新方式

按照示例一给的数组
[-1,0,3,5,9,12] target = 9
如果我们定义一个参数mid表示数组的中间索引(下标),在本例中left = 0, right = n - 1 = 5,mid = (left + right) / 2 = 2

nums[mid]就为3,此时我们判断,nums[mid] < target,并且由于数组是升序的,得出 --> nums[mid]之前的数也肯定小于target,所以可以直接不考虑了。

根据之前的判断,就可以直接考虑mid + 1后面的数了,此时就可以做一个更新,更新区间。left = mid + 1, right不变。

按照示例二给的数组
[-1,0,3,5,9,12] target = 2
定义一个参数mid表示数组的中间索引(下标),在本例中left = 0, right = n - 1 = 5, mid = 2

nums[mid]为3,nums[mid] > target,由于数组是升序,得出–> nums[mid]后的数也肯定大于target,所以可以直接不考虑了。

根据之前的判断,就可以直接考虑mid - 1后面的数了,此时就可以做一个更新,更新区间。right = mid - 1, left不变。

循环条件

定义变量为left = 0,right = n - 1,也就是为数组的两边,为了方便遍历。

  1. left < right的时候,可以继续循环
  2. left == right 的时候,是否也满足循环的条件呢?
    存在一种可能,当left = right的时候,正好就是答案,所以left = right也属于循环条件

根据上述的写代码:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 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 return mid;
        }

        return -1;
    }
};

需要特别解释的是mid = left + (right - left) / 2,
之所以不使用mid = (left + right) / 2是因为,可能加法可能会导致溢出,所以使用减法。
right - left是两指针之间的距离,(right - left) / 2就是left到mid之间的距离,加上left表示 0 到mid之间之间的距离

总结模板

 while(left <= right)
        {
            int mid = left + (right - left) / 2;
            if(……)  left = mid + 1;
            else if (……) right = mid - 1;
            else return mid;
        }

进阶模板

链接: https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/

在这里插入图片描述
题意理解:在一个非递减的数组中找到目标值的左端点和右端点。如果目标值不存在就返回[-1,-1],如果数组为空也返回[-1,-1]

此题可以很清楚的分为两部分:查找左端点和查找右端点。

查找左端点

在这里插入图片描述
从上图中可以看出,我们可以将数组分为两部分,一部分是小于t另一部分是大于等于t

可以将数组分为两部分是由数组的性质 ---- 非递减得到的

1.循环条件

left < right
为什么left <= right不行?
在这里插入图片描述

2.更新方式

根据之前将数组分为两部分:小于t,大于等于t
定义一个变量mid,表示left 和 right的中间值

  1. 如果mid处于小于t的区间,那么表达式是什么呢?
    nums[mid] < t
    当mid处于小于t的区间,而我们要寻找的内容是等于t的数,所以mid所处的区间显然不是正确的区间(mid所指向的值不可能正好等于t,所以可以将mid去除),我们就要进行优化

优化的方式就是将mid换到另一个区间里,那就是left = mid + 1

  1. 如果mid处于大于等于t的区间,那么表达式是什么呢?
    nums[mid] >= t

    当mid处于大于等于t的区间,而我们要寻找的内容是等于t的数,所以mid所处的区间有可能是正确的区间(mid所指向的值有可能正好等于t,所以不能将mid去除)

优化方式是right = mid
在这里插入图片描述

3.求中点的方式

求中点有两种方式:

  1. mid = left + (right - left) / 2
  2. mid = left + (right - left + 1) / 2
    两种方式在数字个数是奇数的时候没有什么区别,在偶数的时候有区别,不过对于解题没有什么影响。
    在这里插入图片描述
    但是,如果在数组中只有两个数的时候,这个影响就会非常大!!!

在这里插入图片描述
因为我们的更新方式是right = mid,但是在求中点的时候,如果使用mid = left + (right - left + 1) / 2,就会导致mid向上取整,mid = right

right = mid, mid = right导致两个值都不能改变,最后导致死循环

查找右端点

在这里插入图片描述
从上图中可以看出,我们可以将数组分为两部分,一部分是小于等于t另一部分是大于t

1.循环条件

left < right
理由同查找左端点。

2.更新方式

根据之前将数组分为两部分:小于等于t,大于t
定义一个变量mid,表示left 和 right的中间值

  1. 如果mid处于小于等于t的区间,那么表达式是什么呢?
    nums[mid] <= t
    当mid处于小于等于t的区间,而我们要寻找的内容是等于t的数,所以mid所处的区间有可能是正确的区间(mid所指向的值有可能正好等于t,所以不能将mid去除)

优化的方式就是将mid换到另一个区间里,那就是left = mid

  1. 如果mid处于大于t的区间,那么表达式是什么呢?
    nums[mid] > t

    当mid处于大于t的区间,而我们要寻找的内容是等于t的数,所以mid所处的区间显然不是正确的区间(mid所指向的值不可能正好等于t,所以可以将mid去除),我们就要进行优化

优化方式是right = mid - 1
在这里插入图片描述

3.求中点的方式

求中点有两种方式:

  1. mid = left + (right - left) / 2
  2. mid = left + (right - left + 1) / 2

在这里插入图片描述
同样的理由,因为更新的方式是left = mid,所以如果采用向下取整,在数组剩最后两个数的时候,就会导致left = mid,mid = left,最后进入死循环。

总结

1.如何确定更新方式?
当我们判断更新的区间,如果mid在这个区间内有可能是答案,那么就是left = mid或者right = mid

如果mid在这个区间内不可能是答案,那么就是left = mid + 1,right = mid - 1

2.如果确定求中点的方式?
规律:当出现right = mid - 1的时候,求中点的方式就需要+1

当算法的原理讲解完之后,做题就比较简单

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        //1.处理数组为空的情况
        if (nums.size() == 0) return {-1,-1};

        //2.查找左端点
        int left = 0, right = nums.size() - 1, begin = 0;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) left = mid + 1;
            else right = mid;
        }

        //3.特殊情况处理 -- left和right的值不是targert就表示不存在
        if(nums[left] != target) return {-1,-1};
        //4.查找右端点
        begin = left, left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left + 1) / 2;
            if (nums[mid] > target) right = mid - 1;
            else left = mid; 
        }

        return {begin, right};
    }
};

在这里插入图片描述

模板总结

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值