《Hello 算法》搜索章练习精讲:二分查找区间收缩、重复元素边界与算法选型实战 📅 发布时间:2026/9/10 4:10:33 👁 浏览次数: 《Hello 算法》搜索章练习精讲二分查找区间收缩、重复元素边界与算法选型实战【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo本篇以《Hello 算法》搜索章末尾的配套练习对应仓库 ru/docs/chapter_searching/exercises.md为骨架逐题拆解二分查找的区间收缩逻辑、重复元素左右边界与搜索算法选型并给出可直接运行的参考实现。读完本文你将能用双闭区间熟练推导二分查找全过程、正确求解重复元素边界并依据数据规模与查询频率在顺序查找、二分查找、哈希表之间做出合理选择。练习定位与配套资源本组练习紧接搜索章的正文章节用于检验三块核心能力区间收缩的理解能否手推每一轮的(i, j, m)对应正文 ru/docs/chapter_searching/binary_search.md边界处理的细节重复元素场景下“找到即返回”为何错误对应 ru/docs/chapter_searching/binary_search_edge.md 与 ru/docs/chapter_searching/binary_search_insertion.md算法选型的权衡在顺序查找、二分查找、哈希表之间做决策对应 ru/docs/chapter_searching/searching_algorithm_revisited.md 与章节总结 ru/docs/chapter_searching/summary.md。仓库中每一道题都有对应实现与测试数据可运行、可对照例如 codes/python/chapter_searching/binary_search.py、codes/python/chapter_searching/binary_search_edge.py、codes/python/chapter_searching/binary_search_insertion.pyC 语言版本见 codes/c/chapter_searching/binary_search.c。知识巩固一二分查找怎样缩小搜索区间题目在有序数组[2, 5, 8, 12, 16, 23, 38]中查找 16。约定使用双闭区间[i, j]中点取 $m i (j - i) / 2$向下取整。请写出每轮的(i, j, m)、中点元素以及下一步如何缩小区间直到找到目标。逐轮推演参考答案轮次(i, j, m)中点元素下一步1(0, 6, 3)1212 16令i 42(4, 6, 5)2323 16令j 43(4, 4, 4)16找到目标返回索引 4推演背后的原理数组有序因此中点值小于目标时可以排除中点及其左侧目标只可能在[m1, j]中点值大于目标时可以排除中点及其右侧目标只可能在[i, m-1]。每轮区间缩小约一半这正是二分查找 $O(\log n)$ 复杂度的来源。注意本题特意使用 $m i (j - i) / 2$ 而非 $m (i j) / 2$。正文明确指出i与j均为int类型时i j可能超出int范围导致溢出因此通常采用差值折半的写法规避。仓库中的 C 语言实现 正是这一写法的直接体现int binarySearch(int *nums, int len, int target) { // 初始化双闭区间 [0, n-1] 即 i, j 分别指向数组首元素、尾元素 int i 0, j len - 1; // 循环当搜索区间为空时跳出当 i j 时为空 while (i j) { int m i (j - i) / 2; // 计算中点索引 m if (nums[m] target) // 此情况说明 target 在区间 [m1, j] 中 i m 1; else if (nums[m] target) // 此情况说明 target 在区间 [i, m-1] 中 j m - 1; else // 找到目标元素返回其索引 return m; } // 未找到目标元素返回 -1 return -1; }知识巩固二重复元素的左右边界题目在数组[1, 2, 2, 2, 4, 6]中查找数字 2。一名同学用二分查找在索引 2 找到目标后立即返回并说“索引 2 就是数字 2 的左边界”。第 1 问这名同学的说法是否正确不正确。找到一个 2 后立即返回只能保证找到了某一个2无法保证它是最左或最右的 2。在本数组中数字 2 出现的区间是索引 13因此左边界为索引 1右边界为索引 3。索引 2 恰好落在区间中部只是碰巧被中点的取整规则选中。第 2 问查找左边界时若中点元素等于目标接下来应继续搜索哪一侧应继续搜索左侧。即使nums[m] target也不能断定左边没有更小的索引因此在双闭区间写法中令j m - 1把搜索范围压向更左的位置直到区间为空。第 3 问查找右边界时又应继续搜索哪一侧应继续搜索右侧。中点元素等于目标后令i m 1把范围压向更右的位置。这一思想在仓库中有精确实现。binary_search_insertion.py 的插入点查找函数在nums[m] target时同样执行j m - 1循环结束后i恰好指向最左一个targetdef binary_search_insertion(nums: list[int], target: int) - int: 二分查找插入点存在重复元素 i, j 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] while i j: m (i j) // 2 # 计算中点索引 m if nums[m] target: i m 1 # target 在区间 [m1, j] 中 elif nums[m] target: j m - 1 # target 在区间 [i, m-1] 中 else: j m - 1 # 最右一个小于 target 的元素在区间 [i, m-1] 中 # 返回插入点 i return i而 binary_search_edge.py 则在此基础上包装出左、右边界查找左边界等价于“查找 target 的插入点”并校验nums[i] target右边界巧妙转化为“查找target 1的插入点”再减一即j i - 1def binary_search_left_edge(nums: list[int], target: int) - int: 二分查找最左一个 target # 等价于查找 target 的插入点 i binary_search_insertion(nums, target) # 未找到 target 返回 -1 if i len(nums) or nums[i] ! target: return -1 # 找到 target 返回索引 i return i def binary_search_right_edge(nums: list[int], target: int) - int: 二分查找最右一个 target # 转化为查找最左一个 target 1 i binary_search_insertion(nums, target 1) # j 指向最右一个 target i 指向首个大于 target 的元素 j i - 1 # 未找到 target 返回 -1 if j -1 or nums[j] ! target: return -1 # 找到 target 返回索引 j return j该文件自带驱动测试对含重复元素的数组[1, 3, 6, 6, 6, 6, 6, 10, 12, 15]分别查找 6 与 7可直观验证“找到即返回”与“边界查找”的结果差异。另一种优雅思路是构造target ± 0.5的“虚拟元素”参与查找数组不含小数不会误匹配详见 ru/docs/chapter_searching/binary_search_edge.md。知识巩固三不同数据该选哪种搜索方法题目请在“顺序查找、二分查找、哈希表”中为下面三个场景选择合适的方法并说明理由。第 1 问在 $10^7$ 个已经有序且不再变动的整数中反复查找不额外建立其他数据结构。选二分查找数据有序且静态二分查找单次 $O(\log n)$且无需额外空间。以 $n 2^{20}$ 为例线性查找需要 $2^{20} 1048576$ 次迭代而二分查找只需 $\log_2 2^{20} 20$ 次迭代差距极其显著。第 2 问在频繁插入、删除的数据集中反复判断某个键是否存在不要求保持有序也不进行范围查找。选哈希表当哈希函数能把键较均匀地分散到各桶时插入、删除和按键查找的平均时间复杂度都是 $O(1)$。若用二分查找为维持数组有序每次插入都要移动元素到指定位置代价高达 $O(n)$若用顺序查找每次查询又是 $O(n)$。第 3 问在无序数组中只查找一次某个值。选直接从头到尾遍历只查找一次时排序$O(n \log n)$或建立哈希表$O(n)$ 预处理都要先处理整个数组并不会减少这一次任务的总工作量反而增加了额外开销。归纳选择取决于数据是否有序、是否允许建立额外结构、查询次数以及需要执行哪些操作。正文 ru/docs/chapter_searching/searching_algorithm_revisited.md 给出了四类方法的时间复杂度对比线性查找二分查找树查找哈希查找查找元素$O(n)$$O(\log n)$$O(\log n)$$O(1)$插入元素$O(1)$$O(n)$$O(\log n)$$O(1)$删除元素$O(n)$$O(n)$$O(\log n)$$O(1)$额外空间$O(1)$$O(1)$$O(n)$$O(n)$预处理/排序 $O(n \log n)$建树 $O(n \log n)$建哈希表 $O(n)$数据是否有序不需要需要需要不需要章节总结 ru/docs/chapter_searching/summary.md 给出的实战准则线性查找适合小规模或高频更新的数据集二分查找适合大规模有序静态数据哈希查找适合查询速度要求极高且无需范围查询的场景树查找适合需要维护有序并支持范围查询的大规模动态数据。编程练习一有序数组的二分查找题目给定一个按严格递增顺序排列的整数数组nums和目标值target请使用二分查找找到target若存在返回它的数组索引若不存在返回 -1。解题提示初始区间是left 0、right n - 1非空条件是left right用mid left (right - left) // 2计算中点若nums[mid]小于target就把左边界移到mid 1若nums[mid]大于target就把右边界移到mid - 1相等时立即返回。参考实现对应仓库 codes/python/chapter_searching/binary_search.pydef binary_search(nums: list[int], target: int) - int: 二分查找双闭区间 # 初始化双闭区间 [0, n-1] 即 i, j 分别指向数组首元素、尾元素 i, j 0, len(nums) - 1 # 循环当搜索区间为空时跳出当 i j 时为空 while i j: m (i j) // 2 # 计算中点索引 m if nums[m] target: i m 1 # 此情况说明 target 在区间 [m1, j] 中 elif nums[m] target: j m - 1 # 此情况说明 target 在区间 [i, m-1] 中 else: return m # 找到目标元素返回其索引 return -1 # 未找到目标元素返回 -1自测时建议覆盖三类输入目标在数组中部、目标在两端索引 0 或 n-1、目标不存在验证返回 -1。仓库驱动测试使用的样例为[1, 3, 6, 8, 12, 15, 23, 26, 31, 35]中查找 6。若想对比区间写法同一文件还提供了左闭右开区间版本区间[i, j)空的条件是i j循环条件变为while i j且命中nums[m] target时令j m而非j m - 1。正文推荐优先使用双闭区间写法因为其指针收缩操作对称、不易出错。编程练习二有序数组的插入位置题目给定一个按严格递增顺序排列的整数数组nums和目标值target。若target已在数组中返回它的索引否则返回把target插入数组后仍能保持严格递增顺序的位置。答案可能是 0也可能等于数组长度。请使用二分查找。解题提示答案可能是 0也可能是数组长度 n使用双闭区间时若nums[mid]大于等于target就令right mid - 1继续检查更靠左的位置否则令left mid 1循环结束时left就是插入位置。参考实现无重复元素版对应 codes/python/chapter_searching/binary_search_insertion.pydef binary_search_insertion_simple(nums: list[int], target: int) - int: 二分查找插入点无重复元素 i, j 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] while i j: m (i j) // 2 # 计算中点索引 m if nums[m] target: i m 1 # target 在区间 [m1, j] 中 elif nums[m] target: j m - 1 # target 在区间 [i, m-1] 中 else: return m # 找到 target 返回插入点 m # 未找到 target 返回插入点 i return i其正确性来自二分查找的收敛性质循环结束后指针i指向第一个大于或等于target的元素指针j指向最右一个小于或等于target的元素因此插入点就是i。它同时回答了练习一的关联问题——当数组包含重复元素时插入位置应是最左一个target的索引这要求命中相等分支时仍执行j m - 1继续向左收缩即上一节给出的binary_search_insertion版本。插入点查找还可以进一步复用于左右边界求解与知识巩固二形成完整闭环。更详尽的推导见 ru/docs/chapter_searching/binary_search_insertion.md。验证与进一步阅读练习完成后建议按如下顺序验证与深化跑通驱动测试在仓库codes/目录下按语言运行对应文件例如 Python 侧直接执行 binary_search.py、binary_search_edge.py、binary_search_insertion.pyC 语言版本参考 binary_search.c编译命令见同目录 CMakeLists 或 Dockerfile 环境。动手改写尝试把“双闭区间”写法改写为“左闭右开”写法并自行实现右边界查找的朴素版本命中相等时令i m 1与仓库中的binary_search_right_edge对比结果。回归正文若某一步推演卡壳回到 ru/docs/chapter_searching/binary_search.md 复习区间定义与收缩规则回到 ru/docs/chapter_searching/binary_search_edge.md 复习边界查找的两种转化技巧最后用 ru/docs/chapter_searching/summary.md 复盘整个搜索章的知识体系。本组练习覆盖了搜索章最容易被忽略的三个易错点中点计算防溢出、重复元素边界“找到即返回”的误区、以及搜索算法选型的权衡逻辑。逐题手推一遍再对照仓库源码跑通驱动测试即可将二分查找从“背模板”升级为“可推导、可变形、可迁移”的稳定能力。【免费下载链接】hello-algo《Hello 算法》动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語提供 Python, Java, C, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考