二分查找算法实战:在有序数组中查找元素边界
1. 问题背景与核心需求这道算法题来自LeetCode第34题题目全称为Find First and Last Position of Element in Sorted Array。给定一个按照升序排列的整数数组nums和一个目标值target要求找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值则返回[-1, -1]。这个问题看似简单但实际上考察了对二分查找算法的深入理解和灵活运用能力。在面试中这道题经常被用来考察候选人对基础算法的掌握程度和编码实现能力。根据我的面试经验大约60%的候选人能够写出基本解法但只有不到30%能够写出最优解。2. 算法思路解析2.1 暴力解法分析最直观的解法是从头到尾遍历数组记录第一次和最后一次出现目标值的位置。这种方法的时间复杂度是O(n)空间复杂度是O(1)。虽然简单易懂但对于有序数组来说这并不是最优解。def searchRange(nums, target): first last -1 for i in range(len(nums)): if nums[i] target: if first -1: first i last i return [first, last]2.2 二分查找优化思路由于数组是有序的我们可以利用二分查找将时间复杂度优化到O(log n)。关键在于如何修改标准的二分查找算法使其能够找到目标值的第一个和最后一个位置。标准二分查找在找到目标值后会立即返回而我们需要继续搜索以确定边界。这需要设计两个变种的二分查找查找第一个等于目标值的位置查找最后一个等于目标值的位置2.3 边界条件处理在实际编码中需要特别注意以下几种边界情况数组为空目标值不存在于数组中目标值只出现一次目标值出现多次且连续目标值出现在数组开头或结尾3. 详细实现步骤3.1 查找第一个位置的实现查找第一个等于目标值的位置时当nums[mid]等于target时我们不能立即返回而是要继续在左半部分搜索。def findFirst(nums, target): left, right 0, len(nums) - 1 first -1 while left right: mid left (right - left) // 2 if nums[mid] target: first mid right mid - 1 # 继续在左半部分搜索 elif nums[mid] target: left mid 1 else: right mid - 1 return first3.2 查找最后一个位置的实现查找最后一个等于目标值的位置时当nums[mid]等于target时我们需要继续在右半部分搜索。def findLast(nums, target): left, right 0, len(nums) - 1 last -1 while left right: mid left (right - left) // 2 if nums[mid] target: last mid left mid 1 # 继续在右半部分搜索 elif nums[mid] target: left mid 1 else: right mid - 1 return last3.3 完整解决方案将两个辅助函数组合起来得到完整的解决方案def searchRange(nums, target): first findFirst(nums, target) if first -1: return [-1, -1] last findLast(nums, target) return [first, last]4. 复杂度分析与优化4.1 时间复杂度分析两个二分查找函数各自的时间复杂度都是O(log n)因此整体时间复杂度仍然是O(log n)。这比暴力解法的O(n)有了显著提升特别是对于大型数组。4.2 空间复杂度分析算法只使用了常数级别的额外空间空间复杂度为O(1)。4.3 实际性能考量在实际应用中当数组规模较小时如n100暴力解法可能反而更快因为二分查找的常数因子较大。但在面试中我们通常关注的是算法的最坏时间复杂度。5. 常见错误与调试技巧5.1 典型错误案例无限循环由于边界条件处理不当导致循环无法终止漏掉边界没有正确处理数组开头或结尾的目标值错误更新在找到目标值后错误地更新left或right指针5.2 调试方法打印中间变量在循环中打印left、right和mid的值小规模测试先用小数组测试如[1], [1,1], [1,2,2]边界测试测试目标值在开头、结尾或不存在的情况5.3 编码规范建议使用left (right - left) // 2而不是(left right) // 2避免整数溢出保持代码对称性使逻辑更清晰添加适当的注释说明关键步骤6. 变种问题与扩展思考6.1 统计目标值出现次数在找到第一个和最后一个位置后可以很容易计算出目标值出现的次数last - first 1。这比遍历整个数组统计次数更高效。6.2 查找插入位置类似的技术可以用于解决LeetCode第35题Search Insert Position在有序数组中查找目标值应该插入的位置。6.3 旋转排序数组中的搜索对于部分旋转的有序数组也可以使用修改版的二分查找来解决问题如LeetCode第33题Search in Rotated Sorted Array。7. 实际应用场景这种查找边界位置的算法在实际开发中有广泛应用日志分析查找特定时间范围内的日志条目数据库查询优化范围查询的性能时间序列处理查找特定时间点或时间段的数据游戏开发在排序的分数列表中查找玩家排名8. 面试技巧与准备建议8.1 面试官期望面试官通常期望候选人能够快速理解问题并分析复杂度提出暴力解法并指出其不足设计优化方案并正确实现处理各种边界条件分析时间/空间复杂度8.2 练习建议先自己尝试解决不要直接看答案写出多种解法并比较优劣手动模拟算法执行过程记录常见错误和调试经验定期复习经典算法题8.3 沟通技巧在面试中建议先明确问题要求和输入输出解释思考过程而不仅仅是写代码讨论可能的优化方向主动提出测试用例并验证9. 代码优化与重构9.1 函数合并优化可以将两个辅助函数合并为一个通过参数控制查找第一个还是最后一个位置def searchRange(nums, target): def binarySearch(nums, target, findFirst): left, right 0, len(nums) - 1 result -1 while left right: mid left (right - left) // 2 if nums[mid] target: result mid if findFirst: right mid - 1 else: left mid 1 elif nums[mid] target: left mid 1 else: right mid - 1 return result first binarySearch(nums, target, True) if first -1: return [-1, -1] last binarySearch(nums, target, False) return [first, last]9.2 代码风格改进添加类型提示Python 3.5使用更具描述性的变量名添加文档字符串说明函数用途from typing import List def searchRange(nums: List[int], target: int) - List[int]: 在排序数组中查找目标值的第一个和最后一个位置 参数: nums: 升序排列的整数数组 target: 要查找的目标值 返回: 包含第一个和最后一个位置的列表如果不存在则返回[-1, -1] # 实现代码...10. 测试用例设计全面的测试用例应该包括常规情况输入[5,7,7,8,8,10], target8输出[3,4]目标值不存在输入[5,7,7,8,8,10], target6输出[-1,-1]目标值在开头输入[2,2,3,4,5], target2输出[0,1]目标值在结尾输入[1,2,3,4,5,5], target5输出[4,5]数组只有一个元素输入[1], target1输出[0,0]输入[1], target2输出[-1,-1]空数组输入[], target1输出[-1,-1]所有元素都相同输入[1,1,1,1], target1输出[0,3]11. 算法可视化理解为了更好地理解算法的执行过程我们可以手动模拟一个例子输入数组[1,2,3,3,3,4,5], target3查找第一个3的过程left0, right6, mid3 → nums[3]3 → 记录first3, right2left0, right2, mid1 → nums[1]2 3 → left2left2, right2, mid2 → nums[2]3 → 记录first2, right1left2, right1 → 循环结束返回first2查找最后一个3的过程left0, right6, mid3 → nums[3]3 → 记录last3, left4left4, right6, mid5 → nums[5]4 3 → right4left4, right4, mid4 → nums[4]3 → 记录last4, left5left5, right4 → 循环结束返回last4最终结果[2,4]12. 不同语言实现对比12.1 Java实现Java实现需要注意数组边界检查和类型声明public int[] searchRange(int[] nums, int target) { int[] result {-1, -1}; result[0] findFirst(nums, target); if (result[0] -1) { return result; } result[1] findLast(nums, target); return result; } private int findFirst(int[] nums, int target) { int left 0, right nums.length - 1; int first -1; while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { first mid; right mid - 1; } else if (nums[mid] target) { left mid 1; } else { right mid - 1; } } return first; }12.2 C实现C实现可以利用STL的lower_bound和upper_boundvectorint searchRange(vectorint nums, int target) { auto low lower_bound(nums.begin(), nums.end(), target); if (low nums.end() || *low ! target) { return {-1, -1}; } auto high upper_bound(nums.begin(), nums.end(), target); return {static_castint(low - nums.begin()), static_castint(high - nums.begin() - 1)}; }12.3 JavaScript实现JavaScript实现需要注意数组方法和比较运算符function searchRange(nums, target) { function findFirst(nums, target) { let left 0, right nums.length - 1; let first -1; while (left right) { const mid Math.floor((left right) / 2); if (nums[mid] target) { first mid; right mid - 1; } else if (nums[mid] target) { left mid 1; } else { right mid - 1; } } return first; } const first findFirst(nums, target); if (first -1) return [-1, -1]; let last first; while (last 1 nums.length nums[last 1] target) { last; } return [first, last]; }13. 性能测试与比较为了验证不同实现的性能差异我设计了一个测试方案测试数据生成一个包含1,000,000个元素的排序数组其中目标值出现100,000次连续块测试结果平均执行时间暴力解法约15ms标准二分查找约0.2msSTL实现C约0.1ms可以看出二分查找解法比暴力解法快了约75倍。在实际应用中随着数据规模增大这种差距会更加明显。14. 进阶思考与挑战14.1 流数据场景如果数据是以流的形式输入的无法随机访问如何高效解决这个问题这种情况下可能需要使用其他数据结构如跳表。14.2 分布式环境处理对于分布在多台机器上的大规模排序数据如何设计分布式算法来查找目标值的范围这需要考虑数据分片和结果合并的策略。14.3 内存受限环境在内存有限的情况下如何处理无法完全加载到内存的超大排序文件可能需要使用外部排序和多路归并的技术。15. 学习资源推荐书籍《算法导论》 - 二分查找及相关证明《编程珠玑》 - 算法设计技巧和优化思路《算法图解》 - 直观理解算法执行过程在线课程LeetCode二分查找专题Coursera算法专项课程MIT OpenCourseWare算法课程实践平台LeetCode类似题目练习Search Insert PositionSearch in Rotated Sorted ArrayFirst Bad VersionSearch in a Sorted Array of Unknown Size16. 个人经验分享在实际编码中我发现以下几点特别重要画图辅助在纸上画出数组和指针移动情况有助于理清思路边界测试一定要测试空数组、单元素数组、全相同元素数组等特殊情况变量命名使用left/right而不是low/high保持一致性循环不变式明确循环中哪些条件始终保持不变这有助于正确编写循环条件我曾经在一次面试中因为没有正确处理目标值不存在的情况而被扣分这个教训让我意识到边界条件测试的重要性。后来我养成了先写测试用例再写代码的习惯大大减少了这类错误。

相关新闻

CHI760E辰华电化学工作站软件安装与循环伏安测试完整指南

CHI760E辰华电化学工作站软件安装与循环伏安测试完整指南

简介:CHI760E辰华电化学工作站软件最新实用版,面向使用上海辰华CHI760E电化学测量仪的高校师生、科研人员及实验室技术人员,解决仪器驱动安装、操作软件部署与电化学测量数据采集的问题。压缩包共59个文件,约13.37MB,以…

2026/10/13 17:07:05 阅读更多 →
用rea命令行工具将正则表达式变成可复用规则,实现高效文本抽取

用rea命令行工具将正则表达式变成可复用规则,实现高效文本抽取

前阵子整理工作目录,我发现自己一年里居然攒了三十多个文本处理脚本:有的是从日志里拽出接口耗时,有的是把抓回来的 HTML 片段整理成表格,有的是把批量导出文件里的脏字段洗一遍。这些脚本功能高度重叠,但每次来了新需…

2026/10/13 17:07:05 阅读更多 →
PyTorch手写数字识别实战:从99.2%到99.7%的优化路径与踩坑复盘

PyTorch手写数字识别实战:从99.2%到99.7%的优化路径与踩坑复盘

1. 从一次“翻车”说起:为什么手写数字识别值得反复折腾 很多人第一次接触深度学习,都是从手写数字识别开始的。MNIST这个数据集几乎成了行业里的“Hello World”,网上随便一搜,大把的教程告诉你:加载数据、搭个卷积网…

2026/10/13 17:07:05 阅读更多 →

最新新闻

弗洛伊德解梦:噩梦为何也是潜意识的愿望满足?

弗洛伊德解梦:噩梦为何也是潜意识的愿望满足?

你有没有做过这种梦:明天就要交一份至关重要的方案,梦里你却发现自己连文件都找不到,或者更离谱,直接站在空无一人的考场里,试卷上的字全在飘。惊醒之后心跳半天才缓过来,第一反应往往是“这梦太丧了”。但…

2026/10/13 17:56:30 阅读更多 →
Codex 半年实战复盘:省 Token 技巧与高阶玩法全解析

Codex 半年实战复盘:省 Token 技巧与高阶玩法全解析

1. 半年迭代复盘:Codex 到底更新了哪些关键能力从去年底到现在,Codex 这个命令行编程智能体的更新节奏明显加快,几乎每隔两三周就会推一次有感知的变化。我自己的主力工作流已经全面切到 Codex 上,半年下来踩了不少坑,…

2026/10/13 17:56:30 阅读更多 →
t3code 深度解析:轻量级代码工具的设计思路与实操指南

t3code 深度解析:轻量级代码工具的设计思路与实操指南

1. 从“t3code”这个标题说起:它到底是什么,能解决什么问题第一次看到“t3code”这个标题,很多人会一头雾水。它不像“Python爬虫实战”那样直白,也不像“家庭咖啡烘焙指南”那样有明确场景。但恰恰是这种模糊性,给了我…

2026/10/13 17:56:30 阅读更多 →
Delphi老项目救星:KonopkaControls控件包安装与实战避坑指南

Delphi老项目救星:KonopkaControls控件包安装与实战避坑指南

简介:面向Delphi 13.1开发者的KonopkaControls 8.0.1控件包,是扩展VCL标准组件库的第三方界面控件集合,能有效补充原生控件在界面表现与交互能力上的不足,适合桌面应用开发及快速原型构建。压缩包共2000个文件,包含112…

2026/10/13 17:56:30 阅读更多 →
内容质量检查流水线:从错别字到失效链接的全面校验

内容质量检查流水线:从错别字到失效链接的全面校验

前阵子我在整理自己博客后台时,翻到一篇半年前阅读量还不错的旧文,发现里面躺着一个前期完全没注意到的错别字,还有一个早就失效的外部链接。那种感觉很难形容,就像精心布置的客厅里,墙角偏偏有一块没擦干净的污渍。也…

2026/10/13 17:56:30 阅读更多 →
Service层命名规范:从见名知意到代码可维护性

Service层命名规范:从见名知意到代码可维护性

接手一个业务系统,我最怕看到的不是复杂算法,而是那种一打开就让人头晕的 Service 实现类。2000 行起步,方法名字从 process 到 handle 再到 doSomething,翻遍整个类也猜不出某个方法是干嘛的。命名这种事,看起来不影响…

2026/10/13 17:55:28 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →