【Java】再一次踩了整数溢出的坑
一、起因
我在做【力扣】69.x 的平方根 一题的时候,明明觉得逻辑没问题,可答案就是不对。
原题
给你一个非负整数 x
,计算并返回 x
的算术平方根。
由于返回类型是整数,结果只保留整数部分,小数部分将被舍去。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
示例 1
输入:x = 4
输出:2
示例 2
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。
提示
0 <= x <= 231 - 1
我的代码
class Solution {
public int mySqrt(int x) {
//处理特殊值 0 和 1
if (x == 0) {
return 0;
}
if (x == 1) {
return 1;
}
//二分法查找
int l = 1, r = x / 2;
while (l <= r) {
int m = l + ((r - l) / 2);
if (m * m == x) {
return m;
} else if (m * m < x) {
l = m + 1;
} else {
r = m - 1;
}
}
if (r <= l) {
return r;
} else {
return l;
}
}
}
提交结果
输入:2147395599
输出:1073697799
预期结果:46339
二、思考
我反复检查代码的逻辑,都没发现问题出现在哪里,直到我看见 别人的题解。
对比代码之后我恍然大悟,明白自己又踩了整数溢出的坑。
修改后的代码如下
class Solution {
public int mySqrt(int x) {
//处理特殊值 0 和 1
if (x == 0) {
return 0;
}
if (x == 1) {
return 1;
}
//二分法查找
int l = 1, r = x / 2;
while (l <= r) {
int m = l + ((r - l) / 2);
if (m == x / m) {
return m;
} else if (m < x / m) {
l = m + 1;
} else {
r = m - 1;
}
}
return r;
}
}
三、知识点
1. int m = l + ((r - l) / 2)
写 int m = l + ((r - l) / 2)
而不是直接写 int m = (l + r) / 2
是为了防止整数溢出。
解释
-
假设我们写的是
int m = (l + r) / 2
当l
和r
都是非常大的正整数时(接近Integer.MAX_VALUE
,即2147483647
),l + r
可能会超过int
类型的最大表示范围(2^31 - 1)
,这会导致溢出,结果会变成负数,从而导致程序的行为不符合预期。
如:l = 2000000000
,r = 2000000000
,那么l + r = 4000000000
,这是超过int
的最大值2147483647
的,会产生溢出。 -
假设我们写的是
int m = (l + r) / 2
当r > l
时,r - l
不会溢出,它是一个较小的数,(r - l) / 2
也不会产生太大的值,最终加上l
,依然保持在int
范围内。
2. if (m < x / m)
写 m < x / m
而不写 m * m < x
也是为了防止整数溢出。
解释
原理同上。
当 m * m > 2147483647
时会发生整数溢出,结果会变成负数,导致程序的行为不符合预期。
四、结尾
当然,也可以将 m * m < x
改成 (long) m * m < x
,这样就不用担心整数溢出了,因为 long
类型的最大值比 int
类型的最大值大得多。