#LC278. 第一个错误的版本 (bad_version)
第一个错误的版本 (bad_version)
你好,同学!我是你的 OI 教练。今天我们要攻克的是二分查找算法中极具代表性的一类问题——寻找满足条件的第一个边界点。
这道题在 LeetCode 上叫“第一个错误的版本”,在 NOI 竞赛中,它属于 “二分答案” 或 “单调函数边界查找” 的入门范畴。请拿出草稿纸,随我一起拆解。
一、 预备知识点
- 二分查找(Binary Search):在具有单调性质的区间内快速定位。
- 单调性(Monotonicity):本题序列为
[正确, 正确, ..., 错误, 错误]。一旦出现错误,后续全部错误,这构成了使用二分的先决条件。 - 整数溢出处理:当 接近 时,直接计算
(left + right) / 2会导致溢出。 - 判定性函数(Predicate Function):理解如何利用外部提供的布尔接口进行逻辑决策。
二、 题目描述 (NOI 风格)
题目名称:第一个错误的版本 (bad_version)
输入文件:bad_version.in
输出文件:bad_version.out
【问题描述】 你是产品经理,目前正在带领团队开发一个新产品。不幸的是,该产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用一个函数 bool isBadVersion(version) 来判断 version 是否出错。请实现一个算法来查找第一个错误的版本,并确保你调用的 isBadVersion 次数尽可能少。
【输入格式】 第一行包含两个整数 和 ,其中 是版本总数, 是第一个错误的版本(在实际竞赛中,系统会提供交互接口或隐藏 的逻辑,此处为了样例展示给出)。
【输出格式】 输出一个整数,表示第一个错误的版本号。
【样例 1 输入】
5 4
【样例 1 输出】
4
【样例 1 说明】 调用 isBadVersion-3 结果为 false。 调用 isBadVersion-5 结果为 true。 调用 isBadVersion-4 结果为 true。 故 4 是第一个错误的版本。
【样例 2 输入】
1 1
【样例 2 输出】
1
【数据范围限制】
三、 启发式引导:草稿纸上的推演过程
第一步:建立模型
请在纸上画出一排方块,前几个是绿色的(正确),后几个是红色的(错误)。 我们要找的是从绿变红的那个转折点。
第二步:二分逻辑
假设当前查看中间的版本 mid:
- 如果
isBadVersion(mid)是true: 说明mid本身就是错的。那么第一个错的版本可能是mid,也可能在mid的左边。 动作:收缩右边界right = mid。 - 如果
isBadVersion(mid)是false: 说明从1到mid都是对的。第一个错的版本一定在mid的右边。 动作:收缩左边界left = mid + 1。
第三步:防止溢出
在 NOI 竞赛中, 可以达到 以上。
如果 left 和 right 都很大,left + right 会超过 int 的表示范围。
公式优化:使用 mid = left + (right - left) / 2。
四、 算法思路流程图 (C++ 14 伪代码逻辑)
为了确保 Mermaid 渲染成功,我们避开特殊字符:
graph TD
Start(开始) --> Init(初始化 Left 等于 1, Right 等于 n)
Init --> Loop{Left 小于 Right}
Loop -- 否 --> Output(输出 Left 即为结果)
Loop -- 是 --> CalcMid(Mid 等于 Left 加上 -Right减Left-的一半)
CalcMid --> CheckBad{调用 isBadVersion-Mid 结果为真}
CheckBad -- 是 --> MoveRight(说明第一个坏版本在左侧或就是当前Mid, 更新 Right 等于 Mid)
CheckBad -- 否 --> MoveLeft(说明 Mid 还是好的, 更新 Left 等于 Mid 加 1)
MoveRight --> Loop
MoveLeft --> Loop
Output --> End(结束)
五、 复杂度分析与优化建议
1. 时间复杂度分析
- 过程:每次询问都会将搜索区间缩小一半。
- 计算:。
- 思考:对于 ,最多只需要进行 31 次 API 调用。这符合题目“最小化调用次数”的要求。
2. 空间复杂度分析
- 计算:仅使用了常数个辅助变量(
left,right,mid),空间复杂度为 。
3. 优化建议
- 数据类型:在 C++ 中,如果 可能达到 ,虽然
int勉强能存下 ,但right - left绝对安全,而left + right会溢出。务必使用left + (right - left) / 2这种稳健写法。 - 终止条件:使用
while (left < right)。当两者相等时,指向的必然是第一个错误版本。
六、 总结:读题时的关键词
在面对 NOI 级别的二分题目时,请圈出这些关键词:
- “第一个……”:通常暗示我们要找的是单调序列的边界。
- “之后的所有版本都是错的”:这明确告诉我们数据具有单调性(一半 False,一半 True),是二分的前提。
- “尽量少调用”:这是在暗示 的线性扫描会超时,必须使用 算法。
- 数据范围 :这是最高级别的橙色警报,提醒你必须注意整数溢出问题。
同学,思路清晰了吗?现在请在草稿纸上尝试手写这个 while 循环,注意最后返回的是 left 还是 right(提示:两者在结束时相等)。加油!