二分查找实战:左闭右闭 vs 左闭右开,哪种写法更适合你的场景?

张开发
2026/6/10 5:09:23 15 分钟阅读
二分查找实战:左闭右闭 vs 左闭右开,哪种写法更适合你的场景?
二分查找的边界艺术左闭右闭与左闭右开的深度抉择二分查找是每个程序员必须掌握的算法基本功但看似简单的逻辑背后隐藏着令人头疼的边界问题。我第一次在技术面试中手写二分查找时就因为while(left right)还是while(left right)纠结了半天最终写出了一个看似正确却无法处理边界条件的版本。后来才发现这背后其实是两种不同的区间定义思想在博弈——左闭右闭与左闭右开。1. 理解二分查找的本质二分查找的核心思想是通过不断缩小搜索范围来快速定位目标元素。想象你在翻阅一本厚厚的电话簿找某个人的号码——你不会从第一页开始逐页查找而是会先翻到中间位置根据字母顺序决定向前或向后查找这就是二分思想的日常应用。在代码实现中我们需要明确几个关键要素搜索区间当前查找的范围边界终止条件何时停止查找边界更新如何根据中间值调整搜索范围# 二分查找的通用框架 def binary_search(nums, target): left, right 0, len(nums) - 1 # 初始化搜索边界 while ...: # 终止条件 mid left (right - left) // 2 # 防止溢出 if nums[mid] target: return mid elif nums[mid] target: left ... # 调整左边界 else: right ... # 调整右边界 return -1 # 未找到这个框架看似简单但其中的...部分正是不同实现方式的分歧点。让我们深入分析两种主流写法。2. 左闭右闭区间写法详解左闭右闭写法将搜索区间定义为[left, right]即包含左右两端点。这种定义方式最符合人类的直觉思维——区间明确包含两个端点。2.1 核心特点初始边界right len(nums) - 1包含最后一个元素循环条件while left right允许left等于right边界更新nums[mid] target时right mid - 1nums[mid] target时left mid 1def binary_search_closed(nums, target): left, right 0, len(nums) - 1 while left right: mid left (right - left) // 2 if nums[mid] target: return mid elif nums[mid] target: left mid 1 else: right mid - 1 return -12.2 适用场景分析左闭右闭写法特别适合以下情况精确查找当只需要判断目标是否存在或找到其确切位置时明确边界当问题描述中明确提到包含边界或使用方括号表示区间时初学者友好逻辑更直观更容易理解和记忆提示在解决LeetCode 704题(二分查找)时左闭右闭写法是最直接的选择。2.3 典型问题与解决方案问题1为什么循环条件是而不是因为当left right时区间[left, right]仍然包含一个有效元素nums[left]需要进行比较。问题2为什么更新right时要减1因为nums[mid]已经确定不是目标值所以新的右边界应该是mid-1排除已检查的元素。3. 左闭右开区间写法剖析左闭右开写法将搜索区间定义为[left, right)即包含左端点但不包含右端点。这种定义方式在某些情况下能使代码更简洁。3.1 核心特点初始边界right len(nums)不包含最后一个元素循环条件while left right不允许left等于right边界更新nums[mid] target时right midnums[mid] target时left mid 1def binary_search_half_open(nums, target): left, right 0, len(nums) while left right: mid left (right - left) // 2 if nums[mid] target: return mid elif nums[mid] target: left mid 1 else: right mid return -13.2 适用场景分析左闭右开写法在以下场景中表现更优查找插入位置如LeetCode 35题(搜索插入位置)处理半开区间当问题描述使用[left, right)表示法时高级变体实现查找第一个/最后一个出现位置等复杂变种3.3 两种写法的关键对比特性左闭右闭[left, right]左闭右开[left, right)初始right值len(nums) - 1len(nums)循环条件left rightleft rightright更新mid - 1mid查找失败时left含义插入位置插入位置直观性更直观稍抽象4. 高级应用与边界问题掌握了基本写法后我们可以解决更复杂的二分查找变种问题。这些问题的关键在于重新定义匹配条件和边界更新逻辑。4.1 查找第一个出现的位置当数组中有重复元素时我们可能需要找到目标值第一次出现的位置。这时需要修改匹配条件def find_first(nums, target): left, right 0, len(nums) - 1 first -1 while left right: mid left (right - left) // 2 if nums[mid] target: right mid - 1 if nums[mid] target: first mid else: left mid 1 return first4.2 查找最后一个出现的位置类似地我们可以找到目标值最后一次出现的位置def find_last(nums, target): left, right 0, len(nums) - 1 last -1 while left right: mid left (right - left) // 2 if nums[mid] target: left mid 1 if nums[mid] target: last mid else: right mid - 1 return last4.3 处理边界条件的实用技巧防止整数溢出使用mid left (right - left) // 2而非(left right) // 2空数组检查在开始前检查if not nums: return -1提前终止找到目标后立即返回避免不必要的循环调试输出在复杂情况下可以打印每次循环的left、right和mid值注意在实现二分查找时务必考虑数组为空、所有元素相同、目标值不存在等边界情况。5. 工程实践中的选择建议在实际项目中选择哪种写法取决于具体场景和个人偏好。以下是我的经验总结团队一致性如果团队已有约定遵循现有规范问题特征精确查找 → 左闭右闭查找插入位置 → 左闭右开个人熟练度选择你最熟悉、最不容易出错的写法我曾在一个性能关键的系统中使用左闭右开写法实现了快速查找因为它能更自然地处理插入位置问题。但在教学场景中我通常会先讲解左闭右闭版本因为它更符合大多数人的直觉。无论选择哪种写法最重要的是保持一致性并在代码中添加清晰的注释说明区间定义。二分查找的bug往往难以发现良好的代码习惯可以节省大量调试时间。

更多文章