二分查找算法 | 你真的搞懂二分了吗?

这篇具有很好参考价值的文章主要介绍了二分查找算法 | 你真的搞懂二分了吗?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

我身边的人都认为二分查找很简单,但事实真是如此吗?不,并不简单。二分算法有着许多的边界问题,当你写着代码一不小心就会陷入死循环。本篇文章会深入细节详细介绍整数二分算法以及使用二分算法步骤力扣题目练习,并且还会给出二分查找算法模板,下面就然我们来看看吧。


一、二分查找算法介绍

1.二分算法的本质

  • 很多人认为二分算法的本质是单调性,其实并不是,二分和单调性的关系是:有单调性的题目一定可以二分,但是我可以二分的题目不一定非得有单调性,注意这句话非常重要,就是有单调性的话我可以二分,但是没有单调性的话我也有可能可以二分。那么二分算法的本质是什么呢?,二分算法的本质其实是边界

单调性想必大家都不陌生,给出一段有序区间,找到它的中间值mid,如果中间值小于目标值的话那么答案在右边,如果中间值大于目标值的话那么答案在左边。
二分查找算法 | 你真的搞懂二分了吗?
那么边界又是个什么东西呢?我们给定一段区间,在这个区间上我们给定了一种性质,使得在这个区间的右半边是满足这个性质的,在这个区间的左半边是不满足这个性质的。那么此时我们就可以使用二分,来找出满足这个性质和不满足这个性质的边界点。
二分查找算法 | 你真的搞懂二分了吗?

2.二分查找算法思想

算法思路:假设答案在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了答案。

接着我们来看看二分算法的主要思想。现在给我们一个区间,在这个区间上我们给定了一种性质,使得在这个区间的右半边是满足这个性质的,在这个区间的左半边是不满足这个性质的。假设我们现在要找出左区间也就是红色区间的边界点,图中用黄色点标出了,我们应该怎样做呢?
首先我们先确定中间值mid,然后我们写一个check函数,接着根据check函数更新答案所在区间
二分查找算法 | 你真的搞懂二分了吗?
此时mid是满足红色性质的,所以mid落在红色区间内,所以mid是有可能为答案的,这里的答案指的是我们要而分出的边界点也就是黄色点。所以此时答案所在区间就是[mid,R];那我们要更新答案区间,就要让L=mid
二分查找算法 | 你真的搞懂二分了吗?
此时mid是不满足红色性质的,所以mid没有落在红色区间内,此时mid不可能为答案,所以此时答案所在区间就是[L,mid-1];那我们要更新答案区间,就要让R=mid-1

  • 我们每一次更新区间都会使答案落在更新的区间内,那么当区间[ L , R ]只有一个数时,也就是L==R时,那么区间[ L , R ]中的那个数就是我们要找的答案。

二、二分查找算法模板!!!

下面是二分查找算法的模板,这个模板几乎可以胜任所有的二分题,建议背过。

版本1
当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是 r = mid,计算mid时不需要加1。

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = (l + r)/2;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

版本2
当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是l = mid;,此时为了防止死循环,计算mid时需要加1。

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid =(l + r + 1)/2;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

二分模板一共有两个,分别适用于不同情况。但其本质的区别就是mid = ( l + r ) / 2时需不要加上1注意:当更新操作为其更新操作是 r = mid,计算mid时不需要加1。其更新操作是l = mid;,此时为了防止死循环,计算mid时需要加1。
加上1的目的是为了防止死循环,这个我们会在后面的题目解释。
那么这个两个模板要怎么使用呢? 我们来做几道题目,从这几道题目中我来给你们解释。


三、力扣题目练习

704. 二分查找

–>题目链接

二分查找算法 | 你真的搞懂二分了吗?

这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,就要考虑是不是可以用二分法了。

这里我们看了题目,发现可以使用二分法。那么我们就用刚刚的模板来看看吧,那要怎么用呢?

  1. 首先我们先确定中间值mid
  2. 然后我们写一个check函数
  3. 接着根据check函数更新答案所在区间。
  4. 根据更新操作判断需不需要加上1

首先我们先确定中间值mid,现在有一区间[-1,12],我们定义左端点和右端点,接着我们计算出mid的值为(0+5)/2=2;
接着我们写一个check函数,可以看到target为9,那么此时区间是不是满足一个性质,这个性质把区间分为两部分,蓝色部分的值小于9,绿色区间的值大于等于9。 此时我们这个check函数可以定义为if(mid的值>=9),就是判断mid是不是满足大于等于9这个性质。
如果mid满足这个特性就说明它落在绿色区域,此时我们就要更新答案所在区间,此时答案在[L,mid]中,我们的更新操作为R=mid;由于更新操作是R=mid,所以这里的mid = ( l + r ) / 2时不需要加上1!!!,我们自然而然使用第一个模板。

二分查找算法 | 你真的搞懂二分了吗?

但此时mid的下标为2所对应的值时3,它是不满足>=9这个性质的,所以mid此时落在蓝色区域,此时我们就要更新答案所在区间,此时答案在[mid,R]中,我们的更新操作为L=mid+1;
然后我们不断缩小答案所在范围,直到这个区间只有一个数时也就是L==R,那此时区间内唯一的那个数就是我们要找的答案,也就是这个性质的边界点
看看代码实现

		int l=0,r=nums.size()-1;
        while(l < r)
        {
            int mid = (l + r) /2;
            if(nums[mid] >= target) r = mid;
            else l = mid + 1;
        }

         return l;

二分查找算法 | 你真的搞懂二分了吗?

但是当我们提交答案后,发现这个测试样例出错了。

  • target为2,但是这个区间内并没有2,那我们此时二分查找出来的边界点是什么呢?

  • 我们再回到刚刚的样例,我们知道二分查找的本质是处理边界问题,我们给这段区间一个性质,那么这个性质是一定有边界的,所以二分是一定有解的。这里我们找到的性质是target右边是大于等于9的,target左边是小于9的,这个性质把区间分为了两部分,绿色部分是满足这个性质的,蓝色部分是不满足这个性质的。那么我们这里的二分查找出来的这个值就是这个性质的边界点
    二分查找算法 | 你真的搞懂二分了吗?

  • 现在我们回到刚刚报错的测试样例,那如果这个区间里没有这个target呢?我们二分查找出来的这个值就是这个性质的边界点,此时我们的的性质是target右边是大于等于2的,target左边小于2的,也就是说我们会找到这个区间里从左往右第一个大于等于2的点。 这句话很重要,请重复理解。因此我们找到的值为3,它的下标为2,与预期结果-1不符,所以错误。

  • 对此我们可以加一个判断条件,如果我们二分出来的值不等于target的话就return-1;

完整代码

	 int l=0,r=nums.size()-1;
        while(l < r)
        {
            int mid = (l + r) /2;
            if(nums[mid] >= target) r = mid;
            else l = mid + 1;
        }

        if(nums[l] != target) return -1;
         return l;
        

35.搜索插入位置

–题目链接
二分查找算法 | 你真的搞懂二分了吗?
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,就要考虑是不是可以用二分法了。 这里我们再次提起这句话,什么时候考虑使用二分。

  • 可以看到这一题和上一题基本一致,唯一的区别就是这题如果我们的target不在这个区间内,我们就需要返回它将会被按顺序插入的位置的下标。
  • 仔细想想,二分查找是一定有解的,这个解就是区间内性质的边界点
  • 那么如果这个区间内没有这个target这个值,我们是不是二分出来的是这个区间里从左往右第一个大于等于target的点呢,这不正好就是我们要插入的位置吗。

解题过程

  1. 首先我们先确定中间值mid
  2. 然后我们写一个check函数
  3. 接着根据check函数更新答案所在区间。
  4. 根据更新操作判断需不需要加上1

看看代码

		int l=0,r=nums.size()-1;
        while(l<r)
        {
            int mid=l+r>>1;
            if(nums[mid]>=target) r=mid;
            else l=mid+1;
        }
        return l;

但是当我们提交后,没错,又有错误!!
二分查找算法 | 你真的搞懂二分了吗?

  • 此时target值为7,区间内没有7这个值,那我们二分到的应该是大于等于7的第一个位置,但此时我们发现区间内最大的数为6,6是这个区间的最后一个位置,也就是说我们的L和R不能再往后更新了,此时L和R落在6的这个位置,循环结束,我们返回的是6的下标也就是3,但我们的7应该插入到6的后一个位置,也就是下标为4的位置。
  • 解决方法,我们做一个特殊判断,如果target的值大于区间最后一个元素的值那我们直接返回数组最后一个元素的下标加1;
    完整代码
    	int l=0,r=nums.size()-1;
        if(nums[r]<target) 
                return r+1; 
                
        while(l<r)
        {
            int mid=l+r>>1;
            if(nums[mid]>=target) r=mid;//此时更新方式是r=mid,所以求mid不需要加上1
            else l=mid+1;
        }
            
        return l;

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

题目链接
二分查找算法 | 你真的搞懂二分了吗?
这道题目是什么意思呢?假设我们有一组数据,[5,7,7,8,8,10],此时target=8,那我们就返回数据中8的开始下标和结束下标。若数据内不存在target则返回[-1,-1]。
二分查找算法 | 你真的搞懂二分了吗?
老规矩,看做题步骤。
解题过程

  1. 首先我们先确定中间值mid
  2. 然后我们写一个check函数
  3. 接着根据check函数更新答案所在区间。
  4. 根据更新操作判断需不需要加上1
    由于我们要寻找开始下标和结束下标两个位置,所以我们肯定需要俩个二分算法,一个寻找开始下标,一个寻找结束下标,这里我们分开来分析。

首先是寻找开始下标,就拿上面图片的样例来分析,此时这个区间是不是被一个性质分成了两部分,这个性质是什么呢,可以看到开始下标左边的数都是小于8的,开始下标右边的数都是大于等于8的,这个性质把区间分为了两个部分,我们用二分就可以找出这个性质的边界点,也就是这里的开始下标的位置
二分查找算法 | 你真的搞懂二分了吗?

那么具体代码是什么呢?

int l=0,r=nums.size()-1;
 while(l<r)
 {
      int mid=(l+r)/2;
      if(nums[mid]>=target) r=mid;
      else l=mid+1;
 }

二分查找算法 | 你真的搞懂二分了吗?

接着是寻找结束下标,此时这个区间是不是被一个性质分成了两部分,这个性质是什么呢,可以看到结束下标左边的数都是大于等于8的,结束下标右边的数都是大于8的,这个性质把区间分为了两个部分,我们用二分就可以找出这个性质的边界点,也就是这里的结束下标的位置
二分查找算法 | 你真的搞懂二分了吗?

下面是具体代码
可以看到更新方式为L=mid;所以这里的mid = ( l + r ) / 2时需要加上1,这里我们就使用了第二个模板。

int l=0,r=nums.size()-1;
 while(l<r)
 {
       int mid=(l+r+1)/2;
       if(nums[mid]<=target) l=mid;
       else r=mid-1;
 }

下面是完整代码

vector<int> res;
        //如果我们的数据为空,直接返回[-1,-1]
        if(nums.size()==0)
        {
             res.push_back(-1);
             res.push_back(-1);
                return res;
        }
        
        //二分开始下标
        int l=0,r=nums.size()-1;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(nums[mid]>=target) r=mid;
            else l=mid+1;
        }
        //当target值不存在时,return[-1,-1]
        if(nums[l]!=target) 
        {
             res.push_back(-1);
             res.push_back(-1);
            return res;
        }
        res.push_back(l);
		
		//二分结束下标
        l=0,r=nums.size()-1;
        while(l<r)
        {
            int mid=(l+r+1)/2;
            if(nums[mid]<=target) l=mid;
            else r=mid-1;
        }

        res.push_back(l);

        return res;

为什么要加上1

好了,三道题目做完了,不知道你还记得那两个模板吗,一个模板在求mid是需要加上一,一个不需要。但是你知道为什么吗?其实在前面我已经提到过了,是为了防止死循环所以我们加上了一个1,下面我们来分析下;

  • 我们知道当更新方式为L=mid时,这里的mid = ( l + r ) / 2时需要加上1
  • 如果此时L=R-1,那么mid算出来的结果应该为L;这里我们可以带入数据去计算,当L=3,R=4,此时mid=(3+4)/2等于3,然后我们更新答案区间,L=mid,我们就又把3赋值给了L,此时就陷入了死循环
  • 所以我们需要在这里加上1,此时mid的值就为(3+4+1)/2等于4,然后我们更新答案区间,L=mid,此时就把4赋值给了L,这时候L==R,循环就终止了,我们就找到了边界点。

四.浮点数二分算法模板

以上我们介绍的都是整数二分算法,整数二分算法需要考虑许多边界问题,因此细节比较多,但浮点数二分简单多了。这里我们就去介绍了,同样的我会给出浮点数二分算法的模板。

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

这里有两道题目,大家可以自己去尝试做一做。
69.x 的平方根
367.有效的完全平方数
记住我们的做题步骤,然后套用上面的模板。
解题过程

  1. 首先我们先确定中间值mid
  2. 然后我们写一个check函数
  3. 接着根据check函数更新答案所在区间。
  4. 根据更新操作判断需不需要加上1

总结

  • 本篇文章介绍了整数二分算法,二分算法的本质不是单调性,二分和单调性的关系是:有单调性的题目一定可以二分,但是我可以二分的题目不一定非得有单调性,二分算法的本质是处理边界
  • 算法思路:假设答案在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了答案。
  • 然后我们给出了整数二分算法的模板和浮点数二分算法的模板,整数二分的模板是由两个的,其本质的区别就是mid = ( l + r ) / 2时需不要加上1,这个模板几乎可以用来解决所有的二分题,建议背过。
  • 接着我们做了几道力扣题,还记得我们的做题步骤吗。
  1. 首先我们先确定中间值mid
  2. 然后我们写一个check函数
  3. 接着根据check函数更新答案所在区间。
  4. 根据更新操作判断需不需要加上1

然后我们分析了整数模板为什么需要加上1,其原因是为了防止死循环。


好了,这篇文章就分享到这,如果对你有帮助的话,请点赞关注,支持一下吧!文章来源地址https://www.toymoban.com/news/detail-409674.html

到了这里,关于二分查找算法 | 你真的搞懂二分了吗?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Python查找算法】二分查找、线性查找、哈希查找

    目录 1 二分查找算法  2 线性查找算法 3 哈希查找算法         二分查找(Binary Search)是一种用于在有序数据集合中查找特定元素的高效算法。它的工作原理基于将数据集合分成两半,然后逐步缩小搜索范围,直到找到目标元素或确定目标元素不存在。 以下是二分查找的

    2024年02月08日
    浏览(51)
  • 【算法】简单的二分查找算法

    一个简单的二分查找算法:         简单描述:算法由静态方法rank()实现,它接受一个整数键和一个有序的int数组作为参数,如果整数存在于数组,返回它的索引,否则返回-1,算法使用两个变量lo和hi,并保证整数如果存在于数组中则它一定存在于a[lo...hi]中,然后通过循

    2024年01月21日
    浏览(57)
  • 【算法小课堂】二分查找算法

    当我们要从一个序列中查找一个元素的时候,最快想到的方法就是顺序查找法(即:从前到后依次查找)。但这种方法过于无脑,就是暴力的把每个元素都排查一遍。元素个数少的时候还行,一旦元素个数多起来,效率是非常低下,所以在实际中这种查找的方法是被摒弃的。

    2024年02月08日
    浏览(39)
  • Java数据结构与算法:查找算法之二分查找

    大家好,我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编,欢迎回到本专栏。在这个冰冷的季节里,我们将一同探讨Java中一种高效的查找算法——二分查找。让我们点燃知识的火花,一同解锁这个查找奇迹的秘密! 二分查找简介 二分查找,也称为折半查找,

    2024年01月21日
    浏览(46)
  • 数据结构算法--1 顺序查找二分查找

    顺序查找时间复杂度为O(n) 我们可以借助Python中的函数enumerate,通过enumerate遍历列表返回其索引和值 也可以通过列表长度依次遍历: 但是二分查找时间复杂度为O(logn):

    2024年02月12日
    浏览(54)
  • 【算法集训】基础算法:二分查找 | 概念篇

    二分枚举,也叫二分查找,指的就是给定一个区间,每次选择区间的中点,并且判断区间中点是否满足某个条件,从而选择左区间继续求解还是右区间继续求解,直到区间长度不能再切分为止。 由于每次都是把区间折半,又叫折半查找,时间复杂度为 O(logn),和线性枚举的求

    2024年04月11日
    浏览(46)
  • 【算法专题】二分查找

    题目链接 - Leetcode -704.二分查找 Leetcode -704.二分查找 题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 - 1。 示例 1: 输入: nums = [-1, 0, 3, 5, 9, 12], target = 9 输出 : 4 解释 : 9 出现在 n

    2024年02月05日
    浏览(39)
  • 力扣初级算法(二分查找)

    每日一算法:二分法查找 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 left=0,right=nums.length,取mid为中间值 如果nums[mid]==target,返回mid值,循环终止 如果nums[mid]target,就说明从mid到right之间

    2024年02月14日
    浏览(39)
  • golang二分查找算法实现

    项目中使用到有序数组查找特定元素,简单记录下 Golang 中二分查找算法。 二分查找算法是一种在有序数组中查找特定元素的高效算法。它的基本思想是通过不断将查找范围缩小一半,来快速定位目标元素是否存在。该算法要求数组是有序的,这是因为有序数组的特性允许我

    2024年01月25日
    浏览(43)
  • 算法笔记:二分查找

    1.1 概念 二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按有序排列。 二分查找维护查找空间的左、右和中间指示符,并比较查找目标或将查找条件应用于集合的中间值;如果条件

    2024年02月04日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包