当前位置: 首页 > 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;无…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...

uniapp 小程序 学习(一)

利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 &#xff1a;开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置&#xff0c;将微信开发者工具放入到Hbuilder中&#xff0c; 打开后出现 如下 bug 解…...