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 在概念上更清晰,通用性更强
参考文档
代码实现
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;
};
};