题目:
Implement int sqrt(int x)
.
Compute and return the square root of x, where x is guaranteed to be a non-negative integer.
Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned.
Example 1:
Input: 4
Output: 2
Example 2:
Input: 8
Output: 2
Explanation: The square root of 8 is 2.82842..., and since
the decimal part is truncated, 2 is returned.
求一个数的平方主要有两种方法:二分法和牛顿法。
1. 二分法
比如给定的数是x,那么我们从x/2开始试,判断它的平方是大于x还是小于x,如果大于那么就向下折半继续查找,如果小于就向上折半继续查找,直到左右相等的时候停止。思路很简单,代码写起来倒有不少讲究,时间复杂度O(logn):
class Solution {
public:
int mySqrt(int x) {
int l = 1, r = x;
while (l <= r) {
int mid = (l + r) / 2;
if (mid > x / mid) {
r = mid - 1;
}
else {
l = mid + 1;
}
}
return r;
}
};
这个代码其实纠结了我挺久的,也是参考了discussion里大佬们的代码才写出来。自己主要卡的地方在于:
(1)左边从0开始,其实应该从1开始就行。另外据评论区有人说,右边从x/2开始就可以了,但好像没见过这样的代码……
(2)其实我对二分法的停止条件一直很迷茫,不知道什么时候该用<=、什么时候该用<,以及里面的if也是不知道怎么处理相等的情况,还有最后返回应该返回left还是right还是mid……看到这样一个解释,但也还是没能完全理解为什么这样就是正确的:
(3)防止溢出的小技巧:<1>在取mid的时候最好mid = left + (right - left) / 2,防止left + right产生溢出;<2>不要使用mid * mid > x,而采取mid > x / mid,防止mid * mid溢出。
2. 牛顿法
牛顿法非常巧妙,我也是看了好几篇文章(但每篇都没全部看完)然后大概弄懂了它的意思。首先是它的原理的直观解释,参考:https://www.zhihu.com/question/20690553/answer/146104283 的前三部分可以得到一个宏观上的理解,说明为什么可以通过切线来不断逼近曲线的根,后面几部分感觉涉及到数学方面的知识就比较多了,遂放弃。理解这个以后,我们得出了迭代公式:。接下来,由于求平方根的话相当于求的根,因此,代入前面的迭代公式可以得到求平方根的迭代公式为:。另外一个我觉得非常绝妙的解释是这个:https://www.zhihu.com/question/20690553/answer/543620219,把求平方根比作把一个面积为A、长为x的长方形逐渐变成正方形。
于是我们就可以快乐地写代码了:
class Solution {
public:
int mySqrt(int x) {
long x0 = x;
while (x0 * x0 > x) {
x0 = (x0 + x / x0) / 2;
}
return floor(x0);
}
};
这代码看起来简单,实际上我提交了好几遍没跑过test case的submission……第一次我while里面写的是x0 > x / x0,想着可以防止溢出,结果在跑0的时候光荣runtime error……第二次改正了前面这个毛病,然后就暴露了我x0采用double产生的错误,不知道为什么,x=5的时候会TLE,非常神秘,我怀疑是有什么神秘精度问题,sigh。第三次想试试int行不行,发现跑5没问题了就开心地提交了,结果在x=2147395599的时候应该是光荣溢出了……卒。最后只好改用long,嗯,以后遇到这种数字可能巨大的问题,还是用long比较保险呢。