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

LeetCode-035:搜索插入位置,一题学会二分查找

一、题目概述

给定一个升序排列且无重复元素的数组 nums,以及一个目标值 target

要求:

  • 如果目标值在数组中存在,返回它的下标
  • 如果目标值不存在,返回它按顺序应该插入的位置

例如:

示例 1

nums = [1, 3, 5, 6]
target = 5

输出:

2

因为 5 正好在下标 2 的位置。


示例 2

nums = [1, 3, 5, 6]
target = 2

输出:

1

因为 2 不在数组中,但如果按顺序插入,它应该放在 13 之间,也就是下标 1


示例 3

nums = [1, 3, 5, 6]
target = 7

输出:

4

因为 7 比所有元素都大,所以应该插在数组末尾。


这道题的关键要求是:

必须使用时间复杂度为 O(log n) 的算法

这意味着,虽然线性扫描也能做出答案,但它不是这题真正想考察的解法。
这题的核心知识点就是:二分查找


二、为什么这题适合二分查找

二分查找适用的典型场景有两个:

  1. 数组有序
  2. 希望在较低复杂度下快速查找某个位置

而这道题刚好满足:

  • 数组是升序排列
  • 要求时间复杂度是 O(log n)

这正是标准的二分查找场景。

二分查找的核心思想非常简单:

每次都看中间元素,根据比较结果排除掉一半区间。

这样一来,查找范围会越来越小,效率也就非常高。


三、这题不只是“找得到就返回”,还要会“找不到时确定位置”

这题比普通二分查找多了一层要求:

  • 找到目标值时,返回它的下标
  • 没找到目标值时,也不能直接返回 -1
  • 而是要返回它应该插入的位置

这其实正是这题最值得学的地方。

因为它会让人理解:

二分查找不仅可以用来“找元素”,也可以用来“找边界”或“找位置”。


四、标准二分写法

代码实现

def search_insert(nums, target):left = 0right = len(nums) - 1while left <= right:mid = (left + right) // 2if nums[mid] == target:return midelif nums[mid] < target:left = mid + 1else:right = mid - 1return left

五、代码逐行拆解

1. 定义左右边界

left = 0
right = len(nums) - 1

这里表示当前搜索区间是整个数组。

如果数组是:

nums = [1, 3, 5, 6]

那么开始时:

  • left = 0
  • right = 3

2. 只要区间还存在,就继续查找

while left <= right:

只要左边界还没有越过右边界,就说明还有搜索空间。


3. 计算中间位置

mid = (left + right) // 2

这一步是二分查找的核心。

例如当前区间是:

  • left = 0
  • right = 3

那么:

mid = (0 + 3) // 2 = 1

此时就先看 nums[1]


4. 如果中间值正好等于目标值,直接返回

if nums[mid] == target:return mid

这表示已经找到目标值,不需要继续查找。


5. 如果中间值比目标小,去右边找

elif nums[mid] < target:left = mid + 1

因为数组是有序的,所以:

  • mid 左边的元素都不可能是答案
  • 下一轮只需要在右半部分继续查找

6. 如果中间值比目标大,去左边找

else:right = mid - 1

同理,这时右半部分都可以排除掉,只保留左半部分。


7. 如果循环结束仍没找到,返回 left

return left

这句非常关键。

循环结束时,left 所在的位置,正好就是目标值应该插入的位置。

这也是这题和普通“查找元素”二分题最大的区别。


六、为什么最后返回 left

这是这题最值得真正理解的部分。

如果没有找到目标值,循环为什么结束?

因为最后会出现:

left > right

此时搜索区间已经空了。
left 停下来的位置,恰好就是:

第一个大于等于 target 的位置

而这个位置,也正是目标值应该插入的位置。


七、用具体例子理解 return left

例子一:target = 2

nums = [1, 3, 5, 6]
target = 2

开始:

  • left = 0
  • right = 3

第一次循环

mid = 1
nums[mid] = 3

因为:

2 < 3

所以:

right = mid - 1 = 0

第二次循环

现在:

  • left = 0
  • right = 0
mid = 0
nums[mid] = 1

因为:

2 > 1

所以:

left = mid + 1 = 1

循环结束

现在:

  • left = 1
  • right = 0

因为 left > right,循环结束。

返回:

left = 1

这正是 2 应该插入的位置。


例子二:target = 7

nums = [1, 3, 5, 6]
target = 7

最后 left 会走到 4,也就是数组末尾之后的位置。

所以返回:

4

这也正是正确答案。


例子三:target = 0

nums = [1, 3, 5, 6]
target = 0

最后 left 会停在 0,说明它应该插入到最前面。

所以返回:

0

八、时间复杂度为什么是 O(log n)

每一次循环,搜索区间都会缩小一半。

比如长度为 8 的区间:

  • 第一次缩成 4
  • 第二次缩成 2
  • 第三次缩成 1

所以总共只需要大约 log n 次比较。

因此时间复杂度是:

O(log n)

空间上只用了几个变量:

O(1)

这完全满足题目要求。


九、这道题真正训练的是什么

这题表面上是在“找位置”,实际上它训练的是二分查找最基础的几个关键能力:

1. 学会维护搜索区间

需要始终明确:

  • 当前搜索区间是 [left, right]
  • 中间位置是 mid
  • 下一轮应该去哪一半继续查找

2. 学会根据比较结果更新边界

这是二分最核心的动作:

  • nums[mid] < target 时,左边界右移
  • nums[mid] > target 时,右边界左移

3. 学会理解“没找到时的边界含义”

很多初学者只会写“找到返回下标”,但这题更进一步,要求理解:

如果没找到,哪个边界代表插入位置?

答案就是:left

这一点非常值得掌握,因为它是后面很多“查找边界类二分题”的基础。


十、同类题里常见的模板价值

这道题其实是二分查找最经典的模板题之一。

一旦把这题吃透,后面很多题都会轻松很多,例如:

  • 查找某个元素第一次出现的位置
  • 查找某个元素最后一次出现的位置
  • 查找第一个大于等于目标值的位置
  • 查找最后一个小于等于目标值的位置

这些题本质上都和“搜索插入位置”有联系。

所以这题不是只会做就够了,更值得记住的是它的模板价值。


十一、小结

“搜索插入位置”这道题最核心的知识点就是:

在有序数组中使用二分查找,不仅可以找到目标值,还可以在找不到时确定它的插入位置。

标准代码如下:

def search_insert(nums, target):left = 0right = len(nums) - 1while left <= right:mid = (left + right) // 2if nums[mid] == target:return midelif nums[mid] < target:left = mid + 1else:right = mid - 1return left

这段代码的关键不是死记,而是理解两件事:

  1. 为什么要不断缩小搜索区间
  2. 为什么没找到时返回的是 left

把这两点真正想明白,这题就不只是“做出来了”,而是“学会了二分查找”。


十二、结尾

这题是 Easy,但它是非常典型、也非常值得反复练的二分查找入门题。

因为它把二分查找里最重要的几个基础都包含进来了:

  • 区间维护
  • 中点判断
  • 边界更新
  • 插入位置的理解

如果后面继续做二分类题,这道题会是一个非常好的模板起点。

http://www.jsqmd.com/news/502852/

相关文章:

  • web网上村委会业务办理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
  • 3个简单步骤掌握My-TODOs:跨平台桌面待办任务管理终极指南
  • OpenFAST仿真结果分析指南:如何利用.sum和.out文件优化你的风力涡轮机设计
  • 说一下线程之间是如何通信的?
  • 想学AI大模型应用开发,努力的顺序不能反!
  • 一键部署UNIT-00:Berserk Interface至CSDN云原生环境教程
  • 5分钟上手Python3.9:Miniconda镜像创建独立环境,支持SSH远程开发
  • 告别DNS劫持:手把手教你用C/C++和libcurl实现自己的DoH客户端
  • 双歧杆菌基因组分析全流程:从序列下载到基因簇挖掘与同源比对
  • 用户体验3.0(UX 3.0)范式框架
  • 单片机/C语言八股:(十四)const 关键字的作用(和 define 比呢?)
  • 大数据领域数据仓库的元数据生命周期管理
  • 解决VMware ESXi环境下Realtek RTL8125网卡驱动适配问题全指南
  • 企业资源管理系统ERP源码(Java)
  • 问卷设计:从“匠人手工”到“书匠策AI智造”的华丽转身
  • 揭开物种共存之谜:我用Hmsc贝叶斯统计分析了6个专题的数据,发现了这些秘密...
  • 射频工程师避坑指南:CPWG与微带线的7个关键选择标准(附RO4350B板材实测)
  • .NET 开源工作流: Slickflow.NET 工作流引擎关于AI大模型的应用实践
  • AI原生应用领域反馈循环:提升用户体验的关键
  • Qwen3-0.6B-FP8在Java面试题智能解答中的应用实战
  • 基于STM32的数字频率计系统设计与实现解析
  • 问题解决策略数据类型实现训练2
  • fanqienovel-downloader:3大核心功能让小说爱好者实现阅读自由
  • Chart.js金融图表插件:快速创建专业K线图和OHLC图表的最佳实践
  • Moondream2实现智能图像分析:基于卷积神经网络的目标检测实战
  • LaTeXdiff实战指南:高效标注论文修改差异
  • 后浪教育平面设计课程打造高效入门路径 - 速递信息
  • 如何高效一键下载网页视频?m3u8-downloader智能解决方案揭秘
  • 【智能体系统AgentOS】核心14:CLI
  • JT/T 1078流媒体平台对接实战:从设备注册到视频播放的完整流程