二分查找(带图详解)

优选算法系列


文章目录

  • 优选算法系列
  • 前言
  • 一、二分查找的思想
  • 二、算法使用
    • 小总结
  • 三、代码实现
  • 四、二分查找拓展
    • 4.1、查找第一次出现的target
      • 小总结
    • 4.2、target最后出现的位置
      • 小总结
  • 五、代码
  • 总结


前言

在这篇博客中,我会给大家分享二分查找及其扩展。

这是链接->Leetcode二分查找

我们先以常规二分查找来引入。

一、二分查找的思想

接下来的讲解,我们以这个例题为背景来展开叙述。链接已放置上方。

例:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

1.你可以假设 nums 中的所有元素是不重复的。
2.n 将在 [1, 10000]之间。
3.nums 的每个元素都将在 [-9999, 9999]之间。

对于上面那题,如果我们使用暴力查找,当数据量比较大时查找效率时比较低的,而且还没有利用题中给除的,数组有序这个条件。
二分查找的思想是比较简单的,对于有序的数据,我们在查找某一值时:
首先选择数组中间的数字和需要查找的目标值比较
如果相等最好,就可以直接返回答案了
如果不相等:

1.如果中间的数字大于目标值,则中间数字向右的所有数字都大于目标值,全部排除
2.如果中间的数字小于目标值,则中间数字向左的所有数字都小于目标值,全部排除

二分法就是按照这种方式进行快速排除查找的。接下来我们对上面例题,使用二分思想进行画图解决。

二、算法使用

开始时设left为数据左指针,right为数据右指针,mid为中间值指针,target为目标值。
注:以下标作为指针进行计算。
mid=(left+right)/2,此时:
在这里插入图片描述
讲mid指针指向值于目标值比较,我们发现该值小于目标值,因为数组有序,所以mid指针左侧值都小于目标值,这时我们更行left指针,因为我们知道mid及其左侧值均已小于目标值所以:
left=mid+1;
再次进行查找
mid=(left+right)/2,此时:
在这里插入图片描述
再次与目标值进行比较,发现仍小于目标值,再次更新left指针
left=mid+1,再次查找
mid=(left+right)/2,此时:
在这里插入图片描述
将mid指向值与target进行比较,发现与目标值相等,循环结束。
这是查找是,目标值存在的情况下,那么如果目标值不存在呢?接下来我们继续分析.

对与上面相同过程我们就不赘述了。
在这里插入图片描述
我们接着将mid指向的值,与target比较,发现指向值大于目标值,此时我们可以确定,mid右侧值都大于目标值,这时我们就该更新右侧指针right。
right=mid-1;

这时我们就没有继续下去的必要了,因为left已经大于right了。那么当两个指针相等时我们还要继续判断吗?我们就制造除一个这样的场景看看吧。刚好你可以再梳理一遍过程。

首先进入循环:
mid=(left+right)/2,此时:
在这里插入图片描述
接着将mid指向值与target比较,发现小于target,舍弃mid及左边值。更新left
,left=mid+1,再次进入循环
mid=(left+right)/2,此时:

在这里插入图片描述
继续比较,更新left,left=mid+1,此时我们想要的场景就来了,更行left后:
在这里插入图片描述
这时就出现了当left=right时,我们是否继续循环的情况,结果很,明显吧,对于这两个指针指向的值,我们并不能将他排除掉,所以当然要再次进入循环了。

小总结

循环结束条件:左指针处在右指针的右边时(left>right)

细节问题:
1、在有的情况下我们使用mid=(left+right)/2来对指针取中时,如果left与right相加数值较大时(int类型存不下)可能会发生数据截断,这时我们往往采用mid=(right-left)/2+right,来代替上面的方式,至于为什么可以代替,大家举两个示例,计算一下就可以知道.
2、上一条提到的mid=(right-left)/2+left,也许你在其他地方见过mid=(right-left+1)/2+left这种写法,对于普通的二分查找,这两种写法并没有本质的区别,在下面我们会展开讲解

对于普通该念的二分查找,我们必须要求查找数据有序。

三、代码实现

int search(vector<int>& nums, int target) {int left=0,right=nums.size()-1;while(left<=right)//循环继续的条件{int mid=left+(right-left)/2;if(target>nums[mid])left=mid+1;//小于目标值跟新左指针else if(target<nums[mid])right=mid-1;//大于目标值更新右指针else return mid;//找到直接返回,结束循环}return -1;//未找到返回-1}

对于很多新手来说,大家往往被老师说的数组有序给限制了,大家思考一下,我们为什么可以一次排除一半的数据呢?这是因为我们可以从数据中选取一个值,这个值可以将数据分为,两部分(针对这里来说就是,我们可以时刻保证,左边比目标值小或右边比目标值大)而这个性质被称为二段性。其实对于可以将数据分为两部分的,我们都可以考虑,它是否可以使用二分来解决。

四、二分查找拓展

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

接下来我们以这个题为背景来讲解

例:
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

提示:

0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109

这一题,我们使用上面讲的方法,是无法解决的,因为其中含有重复值,数组并非绝对有序,这时可能有人想,利用上面方法找到目标值后,将指针向前移动来找第一次出现的位置,向后移动来找第二次出现的位置不就可以了吗?但是这样的想法,当碰到一些特殊场景(如:目标值为3,待查找空间全为3)就相当于将数据都遍历一遍,这时我们的查找效率就大打折扣了。

上面我们提到,可以利用二分查找的原因并不是数组严格有序,是因为我们可以从中找取一个值将数据分为两部分,即数据具二段性。
在这里插入图片描述
对于上面这组数据,在查target第一次出现的位置时,我们可以利用第一个target,将左边变为小于target的,右边变为大于等于target的。同理查找最后出现的位置,我们利用target将左边变为小于等于target的,右边变为大于target的。这样说是很难理解的,接下来我们直接画图解题。

4.1、查找第一次出现的target

细节问题会在下面统一总结

首先进入循环
mid=left+(right-left)/2,此时:

在这里插入图片描述
比较mid指向的值,于target的关系,发现mid小于target,此时我们所选取的值就可以将数据分为两部分:

不合法区域:一定不含目标值的区域
合法区域:目标值存在区域

这种情况我们肯定是将左侧区域排除的,那么我们怎么更新左指针呢?当然是让他跳出不和法区域了,即:
left=mid+1,此时:
在这里插入图片描述
这样第一轮循环算是结束了,但这时候我们并没有找到目标中,所以再次进入循环:
mid=left+(right-left)/2,此时:

在这里插入图片描述
将mid指向的值,与target比较发现相等,此时我们并不知道mid指向的位置是否为第一个target的值,
但是我们可以确定mid右侧(不包含mid)的值都大于等于target
在这里插入图片描述

所以这时候我们虽然不知到它是否为第一个值,但我们可以确定它右侧一定不包含第一次出现的target,这时我们就可以更新right:
right=mid,为是什么这样更新呢,因为我们无法将mid排除,此时:

在这里插入图片描述
再次进入循环,mid=left+(right-left)/2,此时:
在这里插入图片描述
比较target与mid指向的值,发现相等,继续更新right,right=mid此时:
在这里插入图片描述
这是我们还要进入循环吗?很显然不需要了,当left==right是我们就可以跳出循环,判断他们所指向值是否,等于target,如果相等,我们就找到了,那么如果,待查找值不存在呢?它的结束条件又是什么呢?接下来我们继续看:

为了大家好理解,这次我们以上面的逻辑从头开始。

首先进入循环
mid=left+(right-left)/2,此时:
在这里插入图片描述

将mid指向值与target比较发现小于target,更新left ,left=mid=1,此时:
在这里插入图片描述

再次进入循环,mid=left=(right-left)/2,此时:
在这里插入图片描述

比较…更新left,left=mid+1,此时:

在这里插入图片描述
再次进入mid=left+(right-left)/2,此时:
在这里插入图片描述
比较…更新…相等了就不要再继续了
服啦我画麻了!!!!气死了…啊啊啊。

小总结

1.结束条件:left<right,当left=right时我们只需结束循环,并在循环外进行判断即可。
2.这里不可使用mid=left+(right-left+1)/2,具体原因当我们使用这个对mid进行计算时,有可能进入死循环如:
在这里插入图片描述
当我们次进入循环时,计算的mid仍然不变,这时我们就会更新right,right=mid…再次循环。
这样就造成了死循环问题。

4.2、target最后出现的位置

因为我们要查找最后一次出现的位置,所以要保证左边为小于等于target的,右边为大于target的这样如果存在则left最终指向的就是要查找值,这里大家也继续在过程中体会吧

进入循环,mid=left+(right-left+1)/2(这里和上面有点区别,后面分析),此时:
在这里插入图片描述
将mid指向值与target比较发现,mid指向值满足小于等于target,left=mid,此时:

在这里插入图片描述
再次进入循环,mid=left+(right-left+1)/2,此时:
在这里插入图片描述
继续比较,此时mid指向值仍然小于等于target,再次更新,left=mid,此时:

在这里插入图片描述
进入循环,mid=left+(right-left+1)/2,此时:
在这里插入图片描述
将mid指向值于target比较,发下大于target,更新right,right=mid-1,此时:
在这里插入图片描述

小总结

1.这里不可以使用mid=left+(right-left)/2来跟新mid,不然会造成死循环,大家将两次对比记忆。

五、代码

vector<int> searchRange(vector<int>& nums, int target) {vector<int>v={-1,-1};int left=0,right=nums.size()-1;//初始指针while(left<right){int mid=left+(right-left)/2;if(nums[mid]<target)left=mid+1;else right=mid;}if(left<nums.size()&&nums[left]==target) v[0]=left;left=0;right=nums.size()-1;//重置指针while(left<right){int mid=left+(right-left+1)/2;if(nums[mid]<=target) left=mid;else right=mid-1;}if(right<nums.size()&&nums[right]==target) v[1]=right;return v;}

总结

这里并没有将所以可能遇到的情况罗列出来,入果大家想尝试可以用下面三种模拟:
1.有节果
2.全大于
3.全小于

下面几题,可以使用二分思想来解答:
1.x的平方根
2.搜索插入位置
3.山脉数组的峰顶索引
4.寻找峰值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/486748.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

arguments和纯函数的介绍

认识arguments arguments 是一个 对应于 传递给函数的参数 的类数组(array-like)对象. array-like意味着它不是一个数组类型,而是一个对象类型: □但是它却拥有数组的一些特性,比如说length,比如可以通过index索引来访问;□但是它却没有数组的一些方法,比如forEach、map等; …

煤矿 35kV 变电站 3 套巡检机器人 “上岗”,力破供电瓶颈

近日&#xff0c;杭州旗晟智能科技与甘肃某变电站配电室的三套智能巡检机器人线下测试顺利完成&#xff0c;并成功交付使用&#xff0c;这为电力运维工作注入了全新的活力与强大的技术支撑。 一、项目背景 甘肃某变电站总建筑面积1098平方米的变电站集变电、配电、监控等多功能…

隐式神经网络实现低光照图像增强

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

【人工智能基础06】人工神经网络(练习题):神经网络的计算、激活函数的选择与神经网络的退化

文章目录 1. 基于神经网络计算心理健康程度2. 添加激活函数的神经网络计算3. 使用神经网络预测小胖是否会变胖4. 激活函数选择的讨论5. 神经网络的设计6. 深度线性模型的表达能力线性模型7. 神经网络退化 主要讨论的内容 什么是人工神经网络&#xff0c;相关计算反向传播算法的…

记录Windows中Mysql安装

www.mysql.com 我用的是mysql-installer-community-8.0.25.0.msi 原先安装过,先听了了mysql服务 ctrlshiftesc 先停了服务 控制面板关于mysql的全卸载 不修改的话,默认 如果不修改 自己电脑C盘不想放太多 这里Config Type有三个选项 第一个是测试开发模式 占用内存…

【Java计算机毕业设计】Springboot+vue校园外卖配送服务管理系统【源代码+数据库+LW文档+开题报告+答辩稿+部署教程+代码讲解】

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…

彻底理解ThreadLocal的应用场景和底层实现

一.概念 定义&#xff1a; ThreadLocal 是 Java 中所提供的线程本地存储机制&#xff0c;可以利用该机制将数据缓存在某个线程内部&#xff0c;该线程可以在任意时刻、任意方法中获取缓存的数据。 其实是可以通过调用 Set() 方法往里面存入值&#xff0c;存入的值是每个线程互…

Linux下redis环境的搭建

1.redis的下载 redis官网下载redis的linux压缩包&#xff0c;官网地址:Redis下载 网盘链接&#xff1a; 通过网盘分享的文件&#xff1a;redis-5.0.4.tar.gz 链接: https://pan.baidu.com/s/1cz3ifYrDcHWZXmT1fNzBrQ?pwdehgj 提取码: ehgj 2.redis安装与配置 将包上传到 /…

使用 pyperclip 进行跨平台剪贴板操作

简介&#xff1a;pyperclip 是一个轻量级的 Python 库&#xff0c;支持在不同操作系统&#xff08;Windows、macOS、Linux&#xff09;中进行剪贴板的复制和粘贴。这个库的设计简单易用&#xff0c;非常适合需要频繁进行文本复制粘贴操作的场景。 历史攻略&#xff1a; 使用f…

什么是初积分

在学习《高等动力学》时碰到一个概念“初积分”&#xff0c;为了方便记忆&#xff0c;在这里做个笔记。 1 定义 在常微分方程理论中&#xff0c;初积分是指对于一个给定的常微分方程组&#xff0c;如果存在一个可微函数&#xff0c;使得沿方程组的任何解&#xff0c;函数的值…

S32K324 HSE使用注意事项

文章目录 前言HSE安装完成后APP无法运行问题描述问题产生原因解决方案APP偶发获取不到HSE版本问题描述问题产生原因解决方案使能XRDC后,APP与HSE无法通信问题描述问题产生原因解决方案总结前言 在HSE使用过程中,出现过一些必现和偶发的问题,本文总结一下问题原因和解决方案…

fastadmin 登录退出忽略中间提示页面

背景 研究了一圈CMS&#xff0c;从fastadmin、easyadmin、buildadmin、onethink等等几乎所有的框架CMS&#xff0c;当然也包括若依。 最后&#xff0c;根据当前项目综合考虑&#xff0c;还是选择的fastadmin&#xff1a; 预算经济实惠、维护成本低&#xff1b;工期端&#x…

DApp开发前端框架选择:React还是Vue?

在区块链DApp开发中&#xff0c;前端框架的选择对用户体验和开发效率至关重要。React和Vue作为两大主流前端框架&#xff0c;各自拥有广泛的开发者基础和丰富的生态支持。那么在DApp开发中&#xff0c;该如何选择适合自己的框架呢&#xff1f;下面我们来比较一下&#xff0c;看…

证明网络中的流形成一个凸集

证明网络中的流形成一个凸集 步骤1&#xff1a;定义和符号步骤2&#xff1a;线性组合步骤3&#xff1a;验证容量限制步骤4&#xff1a;验证流量守恒结论示例代码&#xff08;C语言&#xff09; 在网络流理论中&#xff0c;一个流 f f f 是定义在网络图的边集上的一种函数&…

opencv复习

目录 1.core 1.图像变换 1.1 affine仿射变换 1.2 透视变换 2.四元数&#xff08;旋转&#xff09; 2.1 轴角转四元数 2.2 旋转矩阵转四元数 2.3 欧拉角转旋转矩阵 2.4 四元数转旋转矩阵 2.5 四元数用eigen用的比较多 2. imgproc. Image Processing 2.1 bilateralF…

分治_归并_归并排序(逆序对)

912. 排序数组 上一次我们做这道题时用的是数组划分三块的思想搭配随机选择基准元素的⽅法。 随机选择一个数&#xff0c;以这个数key为基准划分数组&#xff0c;小于key的数在左边&#xff0c;大于key的数在右边。再把被划分的两部份再找key值划分&#xff0c;直到只剩1或者0个…

环境兼容: Vue3+ELement-plus

题目&#xff1a;环境兼容&#xff1a; Vue3ELement-plus 前言 身为小白的我也在负责一个项目咯&#xff0c;开发的是Vue3项目&#xff0c;然后就搜阅多篇文章&#xff0c;整理了这个。内容很多是转载的&#xff0c;拼成的我这个文章。 Element-plus简介 Element-plus 是基于…

UE5基本数据类型

bool: 表示布尔值&#xff0c;只有两个取值&#xff1a;true 或 false&#xff0c;用于表示逻辑条件。int8: 表示 8 位的有符号整数&#xff0c;范围是 −128−128 到 127127。uint8: 表示 8 位的无符号整数&#xff0c;范围是 00 到 255255。int16: 表示 16 位的有符号整数&am…

【SpringMVC】参数传递 重定向与转发 REST风格

文章目录 参数传递重定向与转发REST风格 参数传递 ModelAndView&#xff1a;包含视图信息和模型数据信息 public ModelAndView index1(){// 返回页面ModelAndView modelAndView new ModelAndView("视图名");// 或// ModelAndView modelAndView new ModelAndView(…

软件工程 概述

软件 不仅仅是一个程序代码。程序是一个可执行的代码&#xff0c;它提供了一些计算的目的。 软件被认为是集合可执行的程序代码&#xff0c;相关库和文档的软件。当满足一个特定的要求&#xff0c;就被称为软件产品。 工程 是所有有关开发的产品&#xff0c;使用良好定义的&…