博主在学习二分法,做力扣69. x 的平方根这一题的时候,遇到一个内存超限的问题,抓耳挠腮了好久,都没有找到原因在哪:
题目要求不使用 pow(x, 0.5) 或者 x ** 0.5等自带功能,求出一个非负整数的算术平方根。使用二分法的思想本身倒是很简单,通过左右两个指针不断去接近其算术平方根即可。
在检查了几遍自己大体思路没问题后,我开始针对这个测试案例进行思考,最终发现了问题~
先上干货,debug后的正确代码:
class Solution {
public int mySqrt(int x) {
int left=0;
int right=x;
int res=-1;
while(left<=right){
int mid=(left+right)/2;
if((long)mid*mid>x){//在判断条件中,中间指针mid前加上(long),避免mid^2超出int类型上限
right=mid-1;
}else if(mid*mid<=x){
res=mid;
left=mid+1;
}
}
return res;
}
}
我对测试案例进行观察,发现2147395599这个数字好像比较特别,好像是int类型数组的上限也是2147开头的数字,于是去查询了一下:
这里我就发现似乎找到了问题所在了 ,于是写了个脚本测试了一下,到底mid是在哪个地方出问题了:
public static void main(String[] args) {
int a1 = 46341;
int square1 = a1 * a1;
int a2 = 46340;
int square2 = a2 * a2;
System.out.println(square1);
System.out.println(square2);
}
输出:
-2147479015//square1 实际46341^2=2147488281
2147395600//square2
这里由于square被定义为int
类型,因此出现了整数溢出,应该输出输出为2147488281的结果,最后变成了:-2147479015。而本题报错的测试用例2147395599处于2147395600~2147488281
下面我们快来看一下这个内存超限的过程:
我们可以看一下第一次进入这个while
循环的时候,mid就变成了107369780,这时候整型溢出就已经开始了,实际上107369780^2>>x,然而却因为整型溢出,被赋值成负数,从而错误的执行
else if(mid*mid<=x)
这条语句,将left指针变成107369781。
但是如果仅仅是一直进入这个循环,只是left一直往右移动,顶多只是计算结果错误,还不至于内存超限。
坏就坏在mid往右移动的时候,有的计算结果在内存超限后,不光会错误地把mid*mid
的结果变成-2147479015这样的负数,还会因为溢出变成一些错误的正数,因此,还可能会进入if(mid*mid>x)
这个循环,所以,left
与right
指针会像在上图中这样反复横跳,从而导致内存超限。
博主用编译器打印了一下,样最后left
与right
会在哪两个值上一直反复横跳,这是最后输出的结果:
mid -715915930
left -715915929
mid 715739835
left 715739836
mid -715915930
left -715915929
mid 715739835
left 715739836
mid -715915930
left -715915929
结果显示,mid最终在-715915930
与715739835
之间反复横跳,导致内存超限
最后,在判断前加个long
定义一下数据类型就可以了~
这是个小问题,希望大家有则改之,无则加勉,永远都不会受到bug困扰,我们下期再见!