二分查找使用记录(使用low + (high - low) / 2的原因)
记录一下二分查找中遇到的问题,来源于一道简单的力扣题
题目
/* The isBadVersion API is defined in the parent class VersionControl.
bool IsBadVersion(int version); */
public class Solution : VersionControl {
public int FirstBadVersion(int n) {
int low = 1;
int high = n;
while(low < high){
int mid = (low + high) / 2;
if (IsBadVersion(mid)) high = mid;
else low = mid + 1;
}
return low;
}
}
当使用mid = (low + high) / 2;
的时候, 就会产生超时异常, 而换成low + (high - low) / 2
的时候就成功通过
分析原因
查看一下超时的输入,理解一下原因
当给出的输入为n = 2126753390, bad = 1702766719
时
我们手动推导一下其过程(有C编译器的同学可以自己debug
看看)
- 第一次循环
low = 1, n = 2126753390
没有问题 - 第二次循环 因为
bad
在1702766719
, 而此时的mid
为1063376695
mid < bad
因此low = mid + 1
- 第三次循环
low = 1063376696, high = 2126753390
这里就发生了问题
int
类型占四个字节, 即为32
位, 补码形式最大可以表示的正数是 2 32 − 1 = 2147483647 2^{32}-1 = 2147483647 232−1=2147483647 而此时low + high > 2147483647
因此会发生溢出
下面写一下这次溢出的详细过程
low = 1063376696(二进制表示) = 00111111011000011101011100111000
high =2126753390(二进制表示) = 01111110110000111010111001101110
low + high = 10111110001001011000010110100110
因为计算机是用补码形式表示的,且int
类型只能存储32位,因此相加之后的最高位溢出变成了1
使得整体的数值成为负数, 最终导致循环始终无法结束
left + (right - left) / 2
可以通过
最大的例子就是 left=max/2
此时max/2 + (max - max/2)/2
显然不会超过max
因此不会产生溢出,引起超时
正确代码
/* The isBadVersion API is defined in the parent class VersionControl.
bool IsBadVersion(int version); */
public class Solution : VersionControl {
public int FirstBadVersion(int n) {
int low = 1;
int high = n;
while(low < high){
int mid = low + (high - low) / 2;
if (IsBadVersion(mid))
high = mid;
else
low = mid + 1;
}
return low;
}
}