Q59- code278- 第一个错误的版本—— 二分写法含义解析

52 阅读3分钟

Q59- code278- 第一个错误的版本

实现思路

1 方法1:二分查找

1.1 开区间表示: l为已确定的是好版本;r为已确定的是坏版本

1.2 本题不能使用开区间,原因是:

  • n取2^31 - 1时(JS中的最大安全整数之一)
  • r = n + 1 会超出32位有符号整数的范围
  • 当整数溢出后,r可能变成负数或不正确的值
  • 导致二分查找的循环条件 l + 1 < r 可能永远为真

2.1 所以推荐使用 双闭区间 或者 左闭右开区间

  • 这里的 “开” 是指 当前位置的所属分段可确认
  • “闭” 是指 当前位置的所属分段 不可确认

2.2 双闭区间写法关键点 解释

  • 左闭:不可确认分段;右闭:不可确认分段
  • 循环不变量含义:check(l-1)属于分段false; check(r+1)属于分段true
  • 确定了mid及其之前/之后所属分段后,此时就可以缩短 下次待处理的区间范围了

3.1 左闭右开区间写法 关键点解释

  • 标准左闭右开区间写法的 r初始值是 len
  • 本题可以写成 n,而不是len = n+1的原因是:
    • 我们在暗示:假设第 n 个版本就是坏版本
    • 这个假设 在大多数测试用例中都是兼容的

3.2 为什么这样"不标准"的做法仍然正确

  • 核心原因是问题的特殊性质:
  • 单调性保证:一旦出现坏版本,后续所有版本都是坏的
  • 边界条件宽松:由于这种单调性,即使我们的初始 r 设置得"保守"一些(指向实际存在的位置而非虚拟边界),算法的收缩过程仍然能正确找到边界
  • 最佳实践上,还是r = n + 1 在概念上更清晰,通用性更强

参考文档

01- 方法1参考文档

代码实现

1.1 方法1.1- 双闭区间 通用二分法

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)
var solution = function (isBadVersion: any) {
  return function (n: number): number {
    // 左闭:不可确认分段;右闭:不可确认分段
    // 循环不变量含义:check(l-1)属于分段false; check(r+1)属于分段true
    let l = 1, r = n;
    while (l <= r) {
      const mid = l + ((r - l) >> 1);
      if (isBadVersion(mid)) {
        // 此时可以确定 mid之后的所有元素 必然属于分段true了
        // 所以下次 待处理判断的范围,可以 缩小到[l, mid-1]
        // 从而保证 循环不变量成立
        r = mid - 1;
      } else {
        // 同上,此时可确定 mid之前的所有元素 必然属于分段false了
        // 所以下次 待处理判断的范围,可以 缩小到[mid+1, r]
        l = mid + 1;
      }
    }
    // 运行到l===r时,如果check(mid === l)属于true,则 r会取到l-1,
    // 所以最后返回 l 或者 r +1 都可
    return l;
  };
};

1.2 方法1.2- 左闭右开 通用二分法

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)
var solution = function (isBadVersion: any) {
  return function (n: number): number {
    // 左闭:不可确认分段;右开:可确认分段
    // 循环不变量含义:check(l-1)属于分段false; check(r)属于分段true
    let l = 1, r = n + 1;
    while (l < r) {
      const mid = l + ((r - l) >> 1);
      if (isBadVersion(mid)) {
        r = mid;
      } else {
        l = mid + 1;
      }
    }
    return r;
  };
};