当前位置: 首页 > news >正文

LeetCode 278. 第一个错误的版本

LeetCode 278. 第一个错误的版本 解析

这个问题要求找到第一个错误的版本,其中给定一个 API isBadVersion(version) 可以判断某个版本是否错误。由于版本号是有序的,且错误版本之后的所有版本都是错误的,因此可以使用二分查找高效地定位第一个错误版本。

方法思路

  1. 二分查找框架
    初始化左右指针 leftright 分别指向第一个和最后一个版本。
    在每次循环中,计算中间版本 mid,并调用 isBadVersion(mid) 判断:

    • mid 是错误版本,说明第一个错误版本在 mid 或其左侧,更新 right = mid
    • mid 不是错误版本,说明第一个错误版本在 mid 右侧,更新 left = mid + 1
  2. 终止条件
    leftright 相遇时,循环结束,此时 left 即为第一个错误版本。

C++ 代码实现

// The API isBadVersion is already defined for you.
// bool isBadVersion(int version);class Solution {
public:int firstBadVersion(int n) {int left = 1;int right = n;while (left < right) {int mid = left + (right - left) / 2;  // 防止整数溢出if (isBadVersion(mid)) {right = mid;  // 错误版本在 mid 或其左侧} else {left = mid + 1;  // 错误版本在 mid 右侧}}return left;  // 循环结束时,left 即为第一个错误版本}
};

代码解释

  1. 二分查找初始化
    left = 1right = n 分别表示版本范围的左右边界。

  2. 防止整数溢出
    使用 mid = left + (right - left) / 2 代替 mid = (left + right) / 2,避免当 leftright 都很大时发生整数溢出。

  3. 循环条件
    while (left < right) 确保循环在 leftright 相遇时终止。

  4. 条件判断

    • isBadVersion(mid) 为真,说明第一个错误版本在 [left, mid] 范围内,更新 right = mid
    • 若为假,说明第一个错误版本在 [mid+1, right] 范围内,更新 left = mid + 1
  5. 返回结果
    循环结束时,leftright 指向同一个位置,即第一个错误版本。

复杂度分析

  • 时间复杂度:O(log n),每次迭代将搜索范围缩小一半。
  • 空间复杂度:O(1),只需要常数级额外空间。

示例

假设 n = 5,且第一个错误版本是 4
二分查找过程如下:

  1. 初始left = 1, right = 5, mid = 3
    • isBadVersion(3) 返回 falseleft = 4
  2. 第二次迭代left = 4, right = 5, mid = 4
    • isBadVersion(4) 返回 trueright = 4
  3. 终止条件left == right == 4,返回 4

关键点

  1. 二分查找变体
    与传统二分查找不同,这里的 right 指向错误版本,因此更新时直接将 right = mid,而非 mid - 1

  2. 防止溢出
    使用 left + (right - left) / 2 计算中间值,避免整数溢出。

  3. 循环终止条件
    left < right 确保不会陷入死循环,且最终 left 即为所求结果。

这种方法高效且简洁,是解决有序数组中查找特定元素问题的经典思路。

在二分查找解决“第一个错误版本”问题时,while (left < right) 循环的设计是为了精准收缩搜索范围,最终让 leftright 相遇在目标位置(第一个错误版本)。以下从逻辑推导和实例验证两方面详细解释:

核心逻辑:范围收缩的必然性

二分查找的本质是通过不断排除不可能的范围,最终将搜索范围缩小到唯一解。在“第一个错误版本”问题中:

  1. 范围的单调性
    题目明确“错误版本之后的所有版本都是错误的”,即版本序列具有单调性:
    [正确版本, 正确版本, ..., 第一个错误版本, 错误版本, ..., 错误版本]
    这种单调性保证了二分查找的可行性。

  2. 循环条件 left < right 的作用

    • left < right 时,搜索范围至少包含两个版本,需要继续收缩。
    • left == right 时,搜索范围仅剩一个版本,这个版本必然是“第一个错误版本”(因为范围收缩过程中已排除所有不可能的选项),循环终止。

范围收缩的细节

在循环中,通过 isBadVersion(mid) 的结果调整 leftright,确保每次收缩都严格包含目标位置

  • isBadVersion(mid) == true
    mid 是错误版本,说明“第一个错误版本”可能是 mid 或在 mid 左侧(因为左侧可能有更早的错误版本)。因此收缩右边界:right = mid

  • isBadVersion(mid) == false
    mid 是正确版本,说明“第一个错误版本”必然在 mid 右侧(因为 mid 及左侧都是正确的)。因此收缩左边界:left = mid + 1

为什么必然相遇在目标位置?

假设目标位置为 ans(第一个错误版本),证明过程如下:

  1. 初始范围left = 1right = n,显然 ans[left, right] 内。
  2. 每次收缩后
    • mid 是错误版本(mid >= ans),则 right = mid,新范围 [left, right] 仍包含 ans(因为 ans <= mid = right)。
    • mid 是正确版本(mid < ans),则 left = mid + 1,新范围 [left, right] 仍包含 ans(因为 ans > mid = left - 1)。
  3. 范围大小的变化
    每次循环后,范围 [left, right] 的大小严格减小(至少减少 1),因此最终会收缩到 left == right
  4. 终止时的位置
    由于每次收缩都包含 ans,最终 left == right 时,这个位置必然是 ans

实例验证

n = 5,第一个错误版本 ans = 4 为例:

步骤leftrightmid = left + (right-left)/2isBadVersion(mid)调整后范围
1153false(正确版本)left = 4,范围 [4,5]
2454true(错误版本)right = 4,范围 [4,4]
344循环终止(left == right)-结果为 4

可见,最终 leftright 相遇在 4,即目标位置。

总结

while (left < right) 的循环设计通过单调收缩范围,确保每次调整都不遗漏目标位置,最终让 leftright 必然相遇在“第一个错误版本”。这种逻辑适用于所有有序且存在唯一解的二分查找问题,是二分查找的经典应用。

http://www.dtcms.com/a/274145.html

相关文章:

  • 基于生产者消费者模型的线程池【Linux操作系统】
  • mysql中的自增ID
  • 物联网-ESP8266
  • API、MCP Client、MCP Server、LLM之间的业务逻辑关系
  • 医疗预约系统中的录音与图片上传功能实现:Vue3+Uniapp 实战
  • 在线重装 Proxmox VE
  • Swift中SwiftyJSON使用详情
  • 墙裂推荐!McpStore库三行代码为Agent添加MCP能力
  • 业务建模如何让金融数字化转型 “轻” 装上
  • CentOS7环境安装包部署并配置MySQL5.7
  • 什么是proxy
  • 使用浏览器inspect调试wx小程序
  • 构建基于表单配置的 Jenkins 测试项目(接口、UI、APP、Jmeter)
  • 加速市场反馈,助力产品迭代升级​
  • 如何使用 Python 删除 Excel 中的行、列和单元格 – 详解
  • IAR携手矽力杰与普华基础软件,共推RISC-V车规芯片高安全应用落地
  • docker 启动中间件
  • Python 数据建模与分析项目实战预备 Day 2 - 数据构建与字段解析(模拟简历结构化数据)
  • 【Python练习】038. 编写一个函数,检查一个链表是否有环
  • PHY模式,slave master怎么区分
  • 力扣网编程134题:加油站(双指针)
  • Android15 无法接收到应用自身发出的广播分析解决
  • 【牛客刷题】dd爱科学1.0
  • Java 大视界 -- Java 大数据在智能医疗远程手术机器人操作数据记录与分析中的应用(342)
  • 从静到动的创作革命:Midjourney Video V1重塑AI影像叙事
  • 滑动窗口-3.无重复字符的最长子串-力扣(LeetCode)
  • 【Linux】虚拟内存的概念和布局
  • UI自动化神器:Playwright元素定位方法!
  • 新型变种木马正在伪装成Termius入侵系统
  • Kotlin 常用语法糖完整整理