当前位置: 首页 > news >正文

算法小抄6-二分查找

二分查找,又名折半查找,其搜索过程如下:

  • 从数组中间的元素开始,如果元素刚好是要查找的元素,则搜索过程结束
  • 如果搜索元素大于或小于中间元素,则排除掉不符合条件的那一半元素,在剩下的数组中进行查找
  • 由于每次需要排除掉一半不符合要求的元素,这需要数组是已经排好序的或者是有规律可循的

在二分查找中,对于每一个反馈,我们能过滤掉一半的数据,因此二分查找相对于普通遍历是一个高效的查找算法,其时间复杂度为O(logn),虽然二分查找的思想很简单,然而二分查找的细节可不少,这里根据力扣的题型给出两种二分查找的写法

二分查找基础版

题目链接

使用二分查找的方式在一个升序的数组中找到指定的值并且返回,其代码框架如下:

定义left为左指针,right为右指针,搜索的区域在[left,right]中
搜索目标值为targtwhile(left<=right){//得到查找的中点mid=(left+right)/2//对于升序数组当前值小于目标值,目标值应该在当前值的右侧if(mid<target){左指针右移,如今搜索区域变为[mid,right]}else if(mid>target){右指针左移,如今搜索区域变为[left,mid]}else{只剩下等于的情况了,直接返回结果找到了目标值}
}

代码如下:

class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) - 1while left <= right:mid = (right - left) // 2 + leftnum = nums[mid]if num < target:left = mid + 1elif num > target:right = mid - 1else:return midreturn -1

注意,重点来了!!!注意代码中的几个关键点:

  • while循环中的条件是left<=right,这意味着在搜索不到答案退出循环时left=right+1,而在二分查找中我们查找的是[left,right]这个区间,这说明所有的数都被搜索到了,记住这一点,以下我们统称left<=right的这种写法为写法一
  • mid=(right-left)//2+left这个写法其实与mid=(left+right)/2效果是一致的,这么写的目的是为了防止整形溢出的情况,因为left+right相加如果大于整形最大值的话会导致结果异常
  • 在移动左右指针时使用的是right=mid-1和left=mid+1,这是因为mid的值已经在上一轮循环中搜索过了,不需要再进行一次搜索

二分查找进阶版--寻找左右边界

为了引出二分查找的第二种写法,这里给出一道非力扣题型的二分查找

寻找左侧边界

例如对于数组[1,2,2,2,3],查找目标为2,我们需要返回最左边的2的下标1,如果使用写法一我们的代码看起来是下面这样的:

class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) - 1while left <= right:mid = (right - left) // 2 + leftnum = nums[mid]if num < target:left = mid + 1elif num > target:right = mid - 1else:right = mid - 1if(left >= len(nums) || nums[left] != target): return -1return left

这段代码有两个注意点:

  • 由于要找到最左侧的边界,在else条件里(num=target), 我们不再直接返回结果,而是继续的收缩右边界,直到right越界或者找到最左边界坐标
  • 是不是决定很奇怪,为什么最后返回的是left,而且是对left做越界判定,记不记得我在之前说过left<=right的退出条件是left=right+1,我们复盘一下最后一轮循环的情况,你就知道为什么最后返回的是left而且也是对left进行越界的判断了
  • 复盘最后一轮循环的过程,对于数组[1,2,2,2,3],target为2,假设当前mid为1,循环也接近了极限,也就是left=mid=right=1,根据代码,在num=target=2的时候,根据代码right=mid-1,那么最后right是否一定不指向正确的结果1,而left指针还没有被移动,依然指向mid,所以我们最后应该返回left
  • 再来看一种情况对于数组[1,2,2,2,3]如果我们查找的是数字4,由于数组中的数字都小于target,我们会不断的缩小左边界,直到退出循环left=right+1,此时left一定是越界的,因为right从头到尾都没动过,而left=right+1保证了left越界
  • 再来看一种情况对于数组[1,2,2,2,3]如果查找的是数字0,由于数组中的数字都大于target,我们会不断的缩小右边界,直到退出循环left=right+1,由于我们最后判断的是left指针,即使right指针越界了,left=right+1也能保证left刚好在下标0的位置,所以是不需要考虑左侧越界的情况的

讲到这里是不是觉得写法一来解决这样的问题真的很复杂,所以我们接下来要说写法二

写法二

使用写法二来解决左侧边界问题,代码如下:

class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) - 1while left < right:mid = (right - left) // 2 + leftnum = nums[mid]if num < target:left = mid + 1else:right = midreturn left

注意点如下:

  • while循环结束的条件是left<right,这说明循环退出的条件是left==right==mid,最后的返回值我们这里返回的是left,其实返回right也是可以的,因为循环退出的条件是left==mid==right嘛
  • 可以看到另一个不同在于在else条件里我们偏移右指针的时候写的是right=mid,我们合并了num>target和num=target的条件,所以为什么是写成right=mid而不是right=mid-1呢?

left=mid+1和right=mid

  • 对于left=mid+1可以这么理解,mid此时的值一定不是我们需要的值,所以我们可以直接越过.举个例子对于数组[1,2,3,4,4,5,5],如果要搜索数字5,我们第一个搜索到的数字mid=(left+right)/2=nums[3]=4,此时4<target,那么是否一定不是我们需要的结果,那么我们可以直接越过
  • 对于right=mid,mid的值可能是我们需要的值,但是在当前循环中我们无法判断出来,所以我们进行保留,举个列子,对于数组[1,5,5,5],如果要搜索数字5,我们第一个搜索到的数字mid=nums[1]=5,此时的5是下标为1的5,并且也是最终我们需要找到的最左侧边界,此时right=mid这行代码就能帮助我们把结果保留了,而后等待left不断右移知道left==right就能退出循环返回最终结果了

寻找右侧边界

学会了寻找左侧边界,那么右侧边界是否也很容易理解了,其实这里有一个小坑,还需要填一下,这里先给出寻找右侧边界的代码

class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) - 1while left < right:mid = (right - left + 1) // 2 + leftnum = nums[mid]if num > target:right = mid - 1else:left =  midreturn right
  • 相信你已经发现了,在取mid的时候我们在括号里加上了一个1,这是因为程序语言中除法操作后下标向下取整(例如(1+2)/2=1),而在寻找右侧边界的过程中,这个操作会导致死循环,永远退不出循环,举个列子:
  • 两者相除,向下取整,这个操作是不是说明mid在有些情况下会向左偏向,我们在寻找左侧边界的时候,需要它向左偏向因此能退出循环,而在寻找右侧边界时,需要它向右收缩边界,此时是否就和向下取整这个情况矛盾了,此时一左一右就会导致永远进入死循环了,因此需要mid=(right-left+1)//2+left这个操作来打破这个死循环

总结

这里很重要哦,如果理解了这里,就离彻底理解二分写法不远了

  • 写法一适合用于在[left,right]这个范围内找到值,例如最基础的二分写法,这种写法需要判断最后返回的是left还是right,要分析最后一次循环后造成的结果
  • 写法二适合用于在[left,right]这个区间内排除值,然后在left=right=mid的时候找到最后的答案,因此写法二的if条件里写的是target值一定不存在的情况
  • 在写法二中如果else条件里是left=mid这个条件(趋向是收缩左边界和向下取整相反),需要修改mid为(right-left+1)//2+left来避免死循环情况的出现

二分搜索代码很简单,但细节是魔鬼,如果觉得自己理解了可以使用写法二做做这一题,题目链接

相关文章:

算法小抄6-二分查找

二分查找,又名折半查找,其搜索过程如下: 从数组中间的元素开始,如果元素刚好是要查找的元素,则搜索过程结束如果搜索元素大于或小于中间元素,则排除掉不符合条件的那一半元素,在剩下的数组中进行查找由于每次需要排除掉一半不符合要求的元素,这需要数组是已经排好序的或者是有…...

大学四年..就混了毕业证的我,出社会深感无力..辞去工作,从头开始

时间如白驹过隙&#xff0c;一恍就到了2023年&#xff0c;今天最于我来说是一个值得纪念的日子&#xff0c;因为我收获了今年的第一个offer背景18年毕业&#xff0c;二本。大学四年&#xff0c;也就将就混了毕业证和学位证。毕业后&#xff0c;并未想过留在湖南&#xff0c;就回…...

C语言数据结构初阶(6)----链表常见OJ题

CSDN的uu们&#xff0c;大家好&#xff01;编程能力的提高不仅需要学习新的知识&#xff0c;还需要大量的练习。所以&#xff0c;C语言数据结构初阶的第六讲邀请uu们一起来看看链表的常见oj题目。移除链表元素原题链接&#xff1a;203. 移除链表元素 - 力扣&#xff08;Leetcod…...

关键字 const

目录 一、符号常量与常变量 二、const的用法 2.1 const常用方法 2.2 const用于指针 2.2.1 p指针所指的对象值不能改变&#xff0c;但是p指针的指向可以改变 2.2.2 常指针p的指向不能改变&#xff0c;但是所指的对象的值可以改变 2.2.3 p所指对象的指向以及对象的值都不可…...

MybatisPlus------MyBatisX插件:快速生成代码以及快速生成CRUD(十二)

MybatisPlus------MyBatisX插件&#xff08;十二&#xff09; MyBatisX插件是IDEA插件&#xff0c;如果想要使用它&#xff0c;那么首先需要在IDEA中进行安装。 安装插件 搜索"MyBatisX"&#xff0c;点击Install&#xff0c;之后重启IDEA即可。 插件基本用途&…...

Leetcode138. 复制带随机指针的链表

复制带随机指针的链表 第一步 拷贝节点链接在原节点的后面 第二步拷贝原节点的random &#xff0c; 拷贝节点的 random 在原节点 random 的 next 第三步 将拷贝的节点尾插到一个新链表 ,并且将原链表恢复 从前往后遍历链表 ,将原链表的每个节点进行复制&#xff0c;并l链接到原…...

python并发编程多线程

在传统操作系统中&#xff0c;每个进程有一个地址空间&#xff0c;而且默认就有一个控制线程 线程顾名思义&#xff0c;就是一条流水线工作的过程&#xff0c;一条流水线必须属于一个车间&#xff0c;一个车间的工作过程是一个进程 车间负责把资源整合到一起&#xff0c;是一个…...

使用Maven实现Servlet程序

创建Maven项目我们打开idea的新建项目,选中里面Maven即可,如下图:创建完成之后,会看到这样的目录结构其中,main目录存放业务代码,其中的java目录存放的就是java代码,而resources目录存放是程序中依赖的文件,比如:图片,视频等.然后是 test目录,test目录存放的是测试代码.最后一个…...

百度的文心一言 ,没有想像中那么差

robin 的演示 我们用 robin 的演示例子来对比一下 文心一言和 ChatGPT 的真实表现&#xff08;毕竟发布会上是录的&#xff09;。 注意&#xff0c;我使用的 GPT 版本是 4.0 文学创作 1 三体的作者是哪里人&#xff1f; 文心一言&#xff1a; ChatGPT&#xff1a; 嗯&a…...

文心一言发布的个人看法

文心一言发布宣传视频按照发布会上说的&#xff0c;文心一言并非属于百度赶工抄袭Chat-GPT的作品&#xff0c;而是十几年一直布局AI产业厚积薄发的成果&#xff0c;百度在芯片&#xff0c;机器学习&#xff0c;自然语言处理&#xff0c;知识图谱等方面均有相对深厚的积累。 国…...

【C5】111

文章目录bmc_wtd&#xff1a;syscpld.c中wd_en和wd_kick节点对应寄存器&#xff0c;crontab&#xff0c;FUNCNAMEAST2500/2600 WDT切换主备&#xff1a;BMC用WDT2作为主备切换的watchdog控制器AC后读取&#xff1a;bmc处于主primary flash&#xff08;设完后&#xff1a;实际主…...

静态成员,友元函数

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下…...

数学分析课程笔记(张平):函数

01 函数 \quad作为数学分析的第一节课&#xff0c;首先深入了解一下函数。 \quad翻看一些教材可以发现&#xff0c;有些教材将“函数”与“映射”区分为两个概念&#xff0c;有些教材&#xff08;尤其是前苏联时期的一些教材&#xff09;则将其视为一个概念。实际上&#xff0c…...

spring事务 只读此文

文章目录一. 事务概述1.1. MySQL 数据库事务1.2 spring的事务支持:1.2.1 编程式事务&#xff1a;1.2.2 声明式事务1.2.3 事务传播行为&#xff1a;1.2.4 事务隔离级别1.2.5 事务的超时时间1.2.6 事务的只读属性1.2.7 事务的回滚策略二. spring事务&#xff08;注解 Transaction…...

真实的软件测试日常工作是咋样的?

最近很多粉丝问我&#xff0c;小姐姐&#xff0c;现在大环境不景气&#xff0c;传统行业不好做了&#xff0c;想转行软件测试&#xff0c;想知道软件测试日常工作是咋样的&#xff1f;平常的工作内容是什么&#xff1f; 别急&#xff0c;今天跟大家细细说一下一个合格的软件测…...

【UML】软件需求说明书

目录&#x1f981; 故事的开端一. &#x1f981; 引言1.1编写目的1.2背景1.3定义1.4参考资料二. &#x1f981; 任务概述2.1目标2.2用户的特点2.3假定和约束三. &#x1f981; 需求规定3.1 功能性需求3.1.1系统用例图3.1.2用户登录用例3.1.3学员注册用例3.1.4 学员修改个人信息…...

面试官:html里面哪个元素可以让文字换行展示

在HTML中&#xff0c;可以使用 <br> 元素来强制换行&#xff0c;也可以使用CSS的 word-break 或 white-space 属性来实现自动换行。以下是这些方法的具体说明&#xff1a; 1.使用 <br> 元素 <br> 元素可以在文本中插入一个换行符&#xff0c;使文本从该位置…...

XGBoost和LightGBM时间序列预测对比

XGBoost和LightGBM都是目前非常流行的基于决策树的机器学习模型&#xff0c;它们都有着高效的性能表现&#xff0c;但是在某些情况下&#xff0c;它们也有着不同的特点。 XGBoost和LightGBM简单对比 训练速度 LightGBM相较于xgboost在训练速度方面有明显的优势。这是因为Ligh…...

JVM高频面试题

1、项目中什么情况下会内存溢出&#xff0c;怎么解决&#xff1f; &#xff08;1&#xff09;误用固定大小线程池导致内存溢出 Excutors.newFixedThreadPool内最大线程数是21亿(2) 误用带缓冲线程池导致内存溢出最大线程数是21亿(3)一次查询太多的数据&#xff0c;导致内存占用…...

Windows环境下实现设计模式——状态模式(JAVA版)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows环境下如何编程实现状态模式&#xff08;设计模式&#xff09;。不知道大家有没有这样的感觉&#xff0c;看了一大堆编程和设计模式的书&#xff0c;却还是很难理解设计模式&#xff0c;无…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)

2025年能源电力系统与流体力学国际会议&#xff08;EPSFD 2025&#xff09;将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会&#xff0c;EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

小木的算法日记-多叉树的递归/层序遍历

&#x1f332; 从二叉树到森林&#xff1a;一文彻底搞懂多叉树遍历的艺术 &#x1f680; 引言 你好&#xff0c;未来的算法大神&#xff01; 在数据结构的世界里&#xff0c;“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的&#xff0c;它…...

Python网页自动化Selenium中文文档

1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API&#xff0c;让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API&#xff0c;你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...

云原生时代的系统设计:架构转型的战略支点

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 一、云原生的崛起&#xff1a;技术趋势与现实需求的交汇 随着企业业务的互联网化、全球化、智能化持续加深&#xff0c;传统的 I…...