Python 的二分查找为什么总写错边界?先看清这 3 个坑

编程狮(w3cschool.cn) 2026-11-01 07:05:55 浏览数 (59)
反馈

二分查找总写错边界,根因几乎都在三个地方:循环条件是 while left <= right 还是 < 没选对、mid 更新时该 left = mid 还是 mid + 1、以及 nums[mid] 命中后区间怎么收缩。只要把「循环不变量」钉死——「答案一定在 [left, right] 闭区间内」——这三种 off-by-one 就不会再犯。先搞懂不变量的含义,比背模板更管用,因为模板一换场景你就又迷了。下面把循环条件、mid 更新、命中收缩这三个边界坑逐一拆开,并给出可直接套用的闭区间模板,照着写基本不会再 off-by-one。

Python 二分查找总写错边界的三个常见坑

先看结论

  • 坑一:循环条件选错。用 [left, right] 闭区间,就必须是 while left <= right;用 [left, right) 半开区间才用 <。混用必死循环或漏判。
  • 坑二:mid 更新不对。排除 mid 后必须 left = mid + 1 或 right = mid - 1,只写 mid 会原地踏步导致死循环。
  • 坑三:命中后区间不收缩。找左边界/右边界时,命中 nums[mid] 不能直接返回,要据此收缩区间,否则拿不到正确边界。
  • 一招治本:钉死循环不变量。每轮循环开始时,保证答案仍在 [left, right] 内,所有更新都不破坏它。
  • 写完后用几组边界用例验证:空数组、单元素命中、元素不存在、重复元素取最左/最右。

一、先弄清「边界错」到底错在哪

前置条件:你写过或读过用 while 在有序数组里查找的二分代码,并曾遇到过死循环、漏判或取错位置。本文示例基于 Python 列表,语言无关,思路通用。

二分查找的难点从来不是「一半一半」,而是维护区间的语义一致性。最常见的写法用闭区间 [left, right]:初始 left=0, right=len(nums)-1,表示「答案如果存在,一定在这个闭区间里」。只要每一轮更新都守住这条不变量,循环结束时 left > right 就说明答案不存在。

错的根源是:有人前半段当闭区间,更新时却只写 left = mid,破坏不变量;或者循环条件用了 < 却按闭区间理解,导致 left == right 那一轮被跳过。下面逐一拆这三个坑。如果你还想补一下算法基础,可以先过一遍 Python3 教程,把切片、索引、// 整除这几处认清楚,再回来对照。

实操前先安装并配置好 Python 运行环境,运行 python binary_search.py 命令跑通最小用例,用几个边界输入做检查,确认预期能正确返回下标或 -1;若失败,按报错定位到是循环条件还是 mid 更新写错,修复后再验证一遍。坑一多半出在循环条件和区间语义没配对:用闭区间 [left, right] 就必须配 while left <= right,漏掉等号会让 left == right 那一趟被跳过。坑二在 mid 更新偷懒:排除 nums[mid] 后必须 left = mid + 1 或 right = mid - 1,只写 mid 会让区间原地踏步、永远出不了循环。坑三在找左右边界时命中不收缩:nums[mid] == target 不能立刻 return,要记录答案并继续往同侧收缩。把「答案始终在 [left, right] 闭区间」这条不变量钉死,这三处就不会再犯。

二、三个边界坑一次看清

2.1 坑一:循环条件与区间语义不一致

闭区间必须配 while left <= right:

def search(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:          # 闭区间:left==right 时区间非空,还要查
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

若把条件写成 while left < right,当 left == right 且恰是答案时会被漏掉,返回 -1。失败处理:记住「区间定义和循环条件是一对,改一个就得改另一个」,统一用闭区间配 <= 最不容易出错。

2.2 坑二:mid 只更新成 mid,原地踏步死循环

第二个坑在排除 mid 时偷懒:

# 错误:left = mid 不改变「答案可能还在 mid」的事实
if nums[mid] < target:
    left = mid          # 死循环!left 不再前进

正确写法是 left = mid + 1(因为 nums[mid] 已排除,答案在右侧且不含 mid)。同理 right = mid - 1。适用边界:半开区间 [left, right) 时 mid 的更新规则会反过来,别跨写法混用。只写 mid 会让 left 永远卡住,区间不缩小,循环出不来。

2.3 坑三:找边界时命中不收缩

第三个坑出现在「找最左/最右出现位置」。以找最左为例,命中 nums[mid] == target 不能直接 return,因为左边可能还有相同的:

def left_bound(nums, target):
    left, right = 0, len(nums) - 1
    ans = -1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            ans = mid
            right = mid - 1      # 继续往左找更靠左的
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return ans

命中后收缩区间是拿边界的关键,直接 return 只会拿到任意一个匹配项,不是你要的边界。涉及更细的语法与切片操作时,Python 速查手册 能帮你快速回查索引与整除规则。

Python 二分查找三种边界错误决策树

三、最小模板与验证

把不变量钉死后的标准查找模板(闭区间)已在上文给出。防错只需三步自查:

  1. 区间定义和 while 条件是否配对([l,r]↔<=);
  2. 排除 mid 时是否 +1/-1;
  3. 找边界时命中是否继续收缩。

验证与失败处理:写完后用几组用例跑一遍——空数组应返回 -1、单元素命中应返回 0、目标不存在应返回 -1、重复元素找左边界应返回最左下标。若某组出错,回到不变量检查:每轮开始时 [left, right] 是否仍包含答案。这类边界问题在数组、字符串、矩阵类题目里反复出现,把不变量当成铁律能省掉大量调试时间。

Python 二分查找边界前后必查项速记卡

总结

二分查找总写错边界,本质是区间语义没守住:循环条件必须和区间定义配对([left,right] 闭区间配 while left <= right),排除中点必须 left=mid+1 或 right=mid-1 否则死循环,找左右边界时命中不能立刻返回而要继续收缩。把「答案始终在 [left,right] 闭区间内」这条循环不变量钉死,三者就不会再犯。模板可以背,但不变量才是真正治本的那一下。

常见问题

Q:为什么我的二分查找出不了循环?

几乎都是 mid 更新没 +1/-1。排除 nums[mid] 后若只写 left = mid,区间不再缩小,left 原地踏步,永远出不了循环。改成 left = mid + 1 即可。

Q:返回 -1 但元素明明在数组里?

最可能是循环条件与区间不一致。用闭区间却写了 while left < right,会跳过 left == right 那一最后一查。统一成 [left,right] + while left <= right 最稳。

Q:怎么找 target 最左/最右出现位置?

命中 nums[mid] == target 时不要直接 return,记录答案并把区间往同侧继续收缩(最左就 right = mid - 1,最右就 left = mid + 1),循环结束返回记录的位置。

Q:mid 用 (left+right)//2 会溢出吗?

Python 整数无上限,不会溢出。但在有长度上限的语言里要写成 left + (right-left)//2 防溢出。习惯上统一这么写也行,可读性稍差但更安全。

延伸学习

0 人点赞