278. 第一个错误的版本

难度:简单 | 主题:二分查找——找左边界

题目

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。要求尽可能少的调用 API。

示例

 
输入:n = 5, bad = 4
 
输出:4
 
解释:调用 isBadVersion(4) → true,第一个错误版本是 4
 

思路

先讲个故事:第一颗坏掉的鸡蛋

你有 100 个鸡蛋一排摆着,从某个位置开始鸡蛋全是坏的。你要找到第一颗坏鸡蛋

你不会从第一颗开始逐个检查——太慢。你会跳着检查:如果第 50 颗是好的,好的鸡蛋后面不会突然变坏,所以坏鸡蛋一定在 51100;如果第 50 颗是坏的,它可能是第一颗,也可能前面还有坏的,所以继续在 149 里找。

这就是”找第一个满足条件的元素”的标准二分。


引导式推导:从线性到二分

线性扫描:调用 isBadVersion(1)isBadVersion(2)… 直到遇到第一个 true。最坏 O(n)。

二分优化:题目核心性质——单调性。一旦某个版本是坏的,它之后的所有版本都是坏的。这形成一个分界线:前段全 true、后段全 false。


graph LR

    subgraph 版本序列

        A["1: ✅"] --> B["2: ✅"]

        B --> C["3: ✅"]

        C --> D["4: ❌<br/>第一个坏版本"]

        D --> E["5: ❌"]

        E --> F["6: ❌"]

    end

问题转化为:在 [1, n] 中找第一个 isBadVersion(mid) == True 的位置。

与 704 的区别

  • 704 找 ==,找到了直接返回

  • 278 找 True,找到了还要往左搜(因为要找第一个)


二分模板:找左边界


graph TD

    L["left=1, right=n"] --> M["mid = (l+r)//2"]

    M --> I{"isBadVersion(mid)?"}

    I -->|True| R["right = mid<br/>继续往左搜"]

    I -->|False| L2["left = mid+1<br/>往右搜"]

    R --> LOOP["循环 left < right"]

    L2 --> LOOP

    LOOP -->|"left==right"| DONE["返回 left"]

为什么 isBadVersion(mid) 为 True 时是 right = mid 而不是 mid - 1 因为 mid 可能就是第一个坏版本,不能跳过。这和 35 题一样的道理。


代码

 
def firstBadVersion(self, n):
 
    left, right = 1, n
 
    while left < right:
 
        mid = left + (right - left) // 2
 
        if isBadVersion(mid):
 
            right = mid
 
        else:
 
            left = mid + 1
 
    return left
 

循环条件为什么是 left < right 而不是 <= 题目保证一定存在坏版本,所以 left == right 时就是答案,不需要再循环。


复杂度

指标解释
时间O(log n)二分版本号
空间O(1)两个指针

实战考量

频率分析

出现在:常考二分变体,考察能否识别单调性以及”找边界”而不是”找值”。

延伸思考

Q:如果找最后一个正确版本呢?

A:条件反转。找最后一个 isBadVersion(mid) == False 的位置:如果 isBadVersion(mid) 为 False 则 left = mid,否则 right = mid - 1。最后返回 right

Q:如果有多个不连续的坏版本呢?

A:本题依赖”坏版本之后全坏”的单调性。如果不连续,不能二分,只能线性扫描。

Q:左闭右开写法怎么写?

A:left, right = 1, n+1while left < right,条件 true 时 right = mid,false 时 left = mid + 1,返回 left

易错点

  • right = mid 不是 mid - 1(mid 可能是答案)

  • 循环条件用 left < right 不是 <=

  • 与 704 的区别:704 找 == 命中即返回,278 找第一个 True 命中不返回


生活类比

找第一个坏版本 → 查污染源

河流上游有工厂排污。你在下游检测到污染,但不知道哪个工厂是源头。你不会逐个工厂测——你从中间工厂测,如果它下游有污染,源头就在上游;如果它下游没污染,源头就在下游。


相关题目

题目关系
704二分查找基础二分模板
34查找元素第一个和最后一个位置找左右边界

→ 返回题单:LeetCode学习路线图 > 八、二分查找

速记卡(面试闪卡)

Q1:一句话讲清「278. 第一个错误的版本」到底是什么?

A:在「坏版本之后全坏」的单调序列里,用二分找第一个坏版本——左边界二分模板,O(log n)。

Q2:题目本质 —— 怎么理解?

A:想象 100 个鸡蛋一排摆着,从某处开始全是坏的,要找第一颗坏蛋。你不会从头查——跳到中间,好则坏蛋在右侧、坏则可能在左侧。这就是「找第一个满足条件的元素」,英语叫 First Bad Version / Lower Bound。

Q3:思路:单调性 → 二分 —— 怎么理解?

A:题目核心性质是单调性:一旦某版本坏,它之后全坏,形成「前段好、后段坏」分界线。问题转化为在 [1,n] 找第一个 isBadVersion==True 的位置。和 704 不同:704 找值命中即返回,278 命中还要往左搜。

Q4:代码:找左边界 —— 怎么理解?

A:循环条件用 left<right 而非 <=,因为题目保证有答案,相等即停。mid 命中时 right=mid(不能 mid-1,mid 可能就是答案),未命中 left=mid+1。像用二分锁死「污染源」位置。

Q5:复杂度与变体 —— 怎么理解?

A:时间 O(log n)(二分版本号),空间 O(1)(两个指针)。变体:找最后一个好版本条件反转;若坏版本不连续(失去单调性)则不能二分,只能线性扫描。

Q6:核心速记主线有哪些?

  • 单调性:坏版本之后全坏,前好后坏一条分界线

  • 左边界二分:命中 right=mid,未命中 left=mid+1

  • 循环用 left<right,结束 left==right 即答案

  • 时间 O(log n)、空间 O(1)

口诀

A:二分查坏版本

左边界要锁定

命中 right 等于 mid

O(log n) 真轻松

相关链接