Leetcode算法入门与数组丨5. 数组二分查找

2023-09-20 22:09:42

1 二分查找算法

二分查找算法是一种常用的查找算法,也被称为折半查找算法。它适用于有序数组的查找,并通过将待查找区间不断缩小一半的方式来快速定位目标值。

算法思想如下:

  1. 首先,确定待查找数组的起始位置(通常为数组的第一个元素)和结束位置(通常为数组的最后一个元素)。
  2. 然后,计算待查找区间的中间位置,即将起始位置和结束位置相加除以2。
  3. 比较中间位置的元素与目标值的大小关系:
  • 如果中间位置的元素等于目标值,则查找成功,返回中间位置。
  • 如果中间位置的元素大于目标值,则目标值可能在左半部分,将结束位置更新为中间位置的前一个位置。
  • 如果中间位置的元素小于目标值,则目标值可能在右半部分,将起始位置更新为中间位置的后一个位置。
  1. 重复步骤2和步骤3,直到找到目标值或者待查找区间为空(起始位置大于结束位置)为止。

二分查找算法的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) ,其中 n n n 为数组的大小。由于每次查找都将待查找区间缩小一半,因此它比线性查找算法更加高效。

2 二分查找细节

区间开闭问题

  • 左闭右闭区间:注意初始化时, r i g h t = l e n ( n u m s ) − 1 right = len(nums)-1 right=len(nums)1,数组最后一个元素位置。
  • 左闭右开区间:注意初始化时, r i g h t = l e n ( n u m s ) right = len(nums) right=len(nums),数组最后一个元素的下一个位置。
  • 一般情况,全部使用「左闭右闭区间」这种写法

m i d mid mid 取值问题

常见的两种取值公式

mid = (left + right) // 2   # 使用较多
mid = (left + right + 1) // 2
  • 当待查找区间中的元素个数为奇数个,使用这两种取值公式都能取到中间元素的下标位置。
  • 当待查找区间中的元素个数为偶数个
    • mid = (left + right) // 2 能取到中间靠左边元素的下标位置。
    • mid = (left + right + 1) // 2 能取到中间靠右边元素的下标位置。

出界条件的判断

两种判断方式

left <= right
left < right
  • left <= right,并且查找的元素不在有序数组中,则 while 语句的出界条件是 left > right,也就是 left == right + 1,写成区间形式就是 [ r i g h t + 1 right+1 right+1, r i g h t right right],此时待查找区间为空,待查找区间中没有元素存在,此时终止循环时,可以直接返回 −1。
  • left < right,并且查找的元素不在有序数组中,则 while 语句出界条件是 left == right,写成区间形式就是 [ r i g h t right right, r i g h t right right]。此时区间不为空,待查找区间还有一个元素存在,我们并不能确定查找的元素不在这个区间中,此时终止循环时,如果直接返回 −1 就是错误的。

使用 left < right 的话,可以在出界之后增加一层判断,判断是否等于目标元素。

# ...
    while left < right:
        # ...
    return left if nums[left] == target else -1

此时,在跳出循环的时候,一定是 left == right,无需判断此时应该返回 left or right

搜索区间范围的选择

三种写法

left = mid + 1,right = mid - 1
left = mid + 1 ,right = mid
left = mid,right = mid - 1

具体哪一种写法和二分查找的两种思路有关。

  • 思路 1:「直接法」—— 在循环体中找到元素后直接返回结果。
  • 思路 2:「排除法」—— 在循环体中排除目标元素一定不存在区间。

3 二分查找两种思路

3.1 直接法

  1. 设定左右边界为数组两端,即 l e f t = 0 , r i g h t = l e n ( n u m s ) − 1 left=0,right=len(nums)−1 left=0right=len(nums)1,代表待查找区间为 [ l e f t , r i g h t ] [left,right] [left,right](左闭右闭区间)。
  2. 取两个节点中心位置 m i d mid mid ,先比较中心位置值 n u m s [ m i d ] nums[mid] nums[mid] 与目标值 t a r g e t target target 的大小。
    1. 如果 t a r g e t = = n u m s [ m i d ] target==nums[mid] target==nums[mid],则返回中心位置。
    2. 如果 t a r g e t > n u m s [ m i d ] target>nums[mid] target>nums[mid] ,则将左节点设置为 m i d + 1 mid+1 mid+1,然后继续在右区间 [ m i d + 1 , r i g h t ] [mid+1,right] [mid+1,right] 搜索。
    3. 如果 t a r g e t < n u m s [ m i d ] target<nums[mid] target<nums[mid],则将右节点设置为 m i d − 1 mid−1 mid1,然后继续在左区间 [ l e f t , m i d − 1 ] [left,mid−1] [left,mid1] 搜索。
  3. 如果左边界大于右边界,查找范围缩小为空,说明目标元素不存在,此时返回 −1。
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        
        # 在区间 [left, right] 内查找 target
        while left <= right:
            # 取区间中间节点
            mid = left + (right - left) // 2
            # 如果找到目标值,则直接范围中心位置
            if nums[mid] == target:
                return mid
            # 如果 nums[mid] 小于目标值,则在 [mid + 1, right] 中继续搜索
            elif nums[mid] < target:
                left = mid + 1
            # 如果 nums[mid] 大于目标值,则在 [left, mid - 1] 中继续搜索
            else:
                right = mid - 1
        # 未搜索到元素,返回 -1
        return -1

3.2 排除法

  1. 设定左右边界为数组两端,即 l e f t = 0 , r i g h t = l e n ( n u m s ) − 1 left=0,right=len(nums)−1 left=0right=len(nums)1,代表待查找区间为 [ l e f t , r i g h t ] [left,right] [left,right](左闭右闭区间)。
  2. 取两个节点中心位置 m i d mid mid ,比较中心位置值 n u m s [ m i d ] nums[mid] nums[mid] 与目标值 t a r g e t target target 的大小,先将目标元素一定不存在的区间排除。
  3. 然后在剩余区间继续查找元素,继续根据条件排除目标元素一定不存在的区间。
  4. 直到区间中只剩下最后一个元素,然后再判断这个元素是否是目标元素。
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        
        # 在区间 [left, right] 内查找 target
        while left < right:
            # 取区间中间节点
            mid = left + (right - left) // 2
            # nums[mid] 小于目标值,排除掉不可能区间 [left, mid],在 [mid + 1, right] 中继续搜索
            if nums[mid] < target:
                left = mid + 1 
            # nums[mid] 大于等于目标值,目标元素可能在 [left, mid] 中,在 [left, mid] 中继续搜索
            else:
                right = mid
        # 判断区间剩余元素是否为目标元素,不是则返回 -1
        return left if nums[left] == target else -1
  • 直接法:更适合解决简单题目,数组中是非重复元素。
  • 排除法:更适合解决复杂题目,数组里可能不存在的元素,找边界问题。

参考文献

  • [1] https://datawhalechina.github.io/leetcode-notes/#/

—— END ——


如果以上内容有任何错误或者不准确的地方,欢迎在下面 👇 留言。或者你有更好的想法,欢迎一起交流学习~~~

更多精彩内容请前往 AXYZdong的博客

更多推荐

MP3算法及代码例程

MP3(MPEG-1AudioLayerIII)是一种数字音频压缩算法,用于对音频进行高效的压缩。MP3算法能够显著减小音频文件的大小,同时保持较高的音质。以下是MP3算法的主要步骤:采样率转换:将输入音频信号的采样率转换为固定的值,通常为44.1kHz。这是因为人耳对于音频的感知范围大约在20Hz到20kHz之间,因

9.3.5网络原理(应用层HTTP/HTTPS)

一.HTTP:1.HTTP是超文本传输协议,除了传输字符串,还可以传输图片,字体,视频,音频.2.3.HTTP协议报文格式:a.首行,b.请求头(header),c.空行(相当于一个分隔符,分隔了header和body),d.正文(body).4.5.URL:唯一资源描述符(长度不限制).a.b.注意:查询字符串(qu

第29章_瑞萨MCU零基础入门系列教程之改进型环形缓冲区

本教程基于韦东山百问网出的DShanMCU-RA6M5开发板进行编写,需要的同学可以在这里获取:https://item.taobao.com/item.htm?id=728461040949配套资料获取:https://renesas-docs.100ask.net瑞萨MCU零基础入门系列教程汇总:https://b

【初阶数据结构】树(tree)的基本概念——C语言

目录一、树(tree)1.1树的概念及结构1.2树的相关概念1.3树的表示1.4树在实际中的运用(表示文件系统的目录树结构)二、二叉树的概念及结构2.1二叉树的概念2.2现实中真正的二叉树2.3特殊的二叉树2.4二叉树的性质2.5二叉树的存储结构一、树(tree)1.1树的概念及结构树是一种非线性的数据结构,它是由n(

Softing物联网(IoT)方案之OT/IT数据集成

一利用数据提高效率和绩效多年以来数据集成和工业物联网一直在推动着市场的发展,目前我们已经能够集成并成功使用先进的技术、大量的传感器和复杂的数据格式等。而在工业物联网或工业4.0中,还有运营技术(OT)和信息技术(IT)它们之间的无缝数据交换对于企业提升竞争力而言同样非常重要。将生产和业务数据深度集成到IT层中可为新的利

【Oracle】Oracle系列--Oracle数据类型

文章目录前言1.字符类型2.数字类型3.大对象类型4.时间及时间间隔类型5.其他类型前言ORACLE基本数据类型,又叫内置数据类型(built-indatatypes)可以按类型分为:字符串类型、数字类型、大对象类型(LOB类型)、日期类型、LONGRAW&RAW类型、ROWID&UROWID类型。1.字符类型数据类型

浅说 MySQL 数据库日志有哪些?作用是什么?

目录1.MySQL日志有哪些?2.各种日志分析2.1错误日志2.2二进制日志2.3通用查询日志2.4慢查询日志2.5中继日志2.6数据定义语句日志2.7补充点3.日志有什么用(优点)4.日志的弊端1.MySQL日志有哪些?MySQL数据库为我们提供了很多种不同类型的日志文件,用来存储不同类型的日志。主要有错误日志,二进

Redis+SpringBoot企业版集群实战------【华为云版】

目录安装复制及集群bgsaverdbaofSpringBoot+Redis操作操作string操作hash操作set操作sortedset获取所有key&删除设置key的失效时间SpringDataRedis整合使用哨兵机制安装下载地址Redis上传至服务器解压tarzxvfredis-5.0.3.tar.gz安装依赖

JS Ajax 封装

ajax封装一、什么是Ajax?二、Ajax的优缺点?2.1优点2.2缺点三、Ajax的使用3.1状态码3.2xhr的基本使用3.3ajax原生封装:3.3.1触发GET请求:3.3.2调用POST请求:四、Ajax的约束一、什么是Ajax?Ajax(AsynchronousJavaScriptAndXML)是2005

【C++杂货铺】国庆中秋特辑——多态由浅入深详细总结

文章目录一、多态的概念二、多态的定义及实现2.1多态的构成条件2.2虚函数2.3虚函数的重写2.4虚函数重写的两个例外2.4.1协变(基类与派生类虚函数返回值类型不同)2.4.2析构函数的重写(基类与派生类析构函数的名字不同)2.5C++11override和final2.5.1final:修饰虚函数,表示该虚函数不能

ORB-SLAM2_RGBD_DENSE_MAP编译、问题解决、离线加载TUM数据和在线加载D435i相机数据生成稠密地图

文章目录0引言1安装依赖1.1其他库安装1.2pcl库安装2编译ORB-SLAM2_RGBD_DENSE_MAP2.1build.sh2.2build_ros.sh3运行ORB-SLAM2_RGBD_DENSE_MAP3.1build.sh编译版本3.2build_ros.sh编译版本0引言ORB-SLAM2_RGBD

热文推荐