https://oj.leetcode.com/problems/sqrtx/
Implement int sqrt(int x).
Compute and return the square root of x.
这一题曾经有一个非常牛逼的做法,叫做牛顿迭代法。大家可以自行谷歌或者百度,大致上就是给一个估计值,然后不停通过某个既定的公式进行迭代最终得到答案。然后我onsite亚马逊的时候被问到过,然后我说:牛顿数学法可以用么?人家第一反应就是:尼玛是做过吧?(当然没有直说)。人家就说不能。。当时,我还不知道别的方法,于是乎...我用了大概六七分钟想到了这个,绝妙的!标准答案.....咳咳。其实也就是二分。在这里,我强烈不推荐牛顿迭代法去应付面试。你不是牛顿,一个面试的时间能够想到牛顿迭代法你就不是在这里被面试了,大概是在某个研究院里面数着自己有几个诺贝尔奖了。只有二分才是正常人类能够在面试的时间内能够想到的做法。Leetcode其实非常仁慈,包括之前的pow,都是整型数作为input和output的格式。所以我们可以以(1, x)范围来作二分查找。小于1的直接返回0。
public int sqrt(int x) {
if(x < 1)
return 0;
int left = 1, right = x;
while(left < right){
int mid = (left + right) / 2;
if(x / mid >= mid && x / (mid + 1) < mid + 1)//本身应该是mid * mid == x 作判断的,之所以这样做的原因是防止溢出
return mid; //另外在之所以同时作mid和mid + 1的判断是因为存在精度流失的情况。所以譬如3的话,本来应该返回1的,但是不判断mid + 1的话就没办法了
else if(x / mid > mid)
left = mid + 1;
else
right = mid - 1;
}
return left;
}
另外,这一题当时我在面试的时候,input和output都是double,这样小于1的时候处理的方式就不是这样了。大家可以思考一下。
2017-12-09 Updated
下面是input output都是double的情况。因为是double,误差值是肯定有的。所以除了x作为input之外,可以延展出一个误差的threshold,以此作为判断的依据。
之前我在这里就提示过,小于1的时候处理的方式和大于1处理的方式并不是一样的。因为大于1的时候sqrt(x)必然是小于x的。但是小于1的时候sqrt(x)就必然是大于x的。所以这个时候二分法用0作为lowerbound和x作为upperbound就不是很合适了。大于1的时候我们是以1为lowerbound,x为upperbound。小于1的时候,我们就可以以x为lowerbound,1为upperbound了。下面给出代码:
public static double sqrt(double x, double threshold) {
double left, right;
if (x >= 1.0) {
left = 1.0;
right = x;
} else {
left = x;
right = 1.0;
}
while (left < right) {
double mid = (left + right) / 2;
double candidate = mid * mid;
if (Math.abs(candidate - x) <= threshold) {
return mid;
} else if(candidate > x) {
right = mid;
} else {
left = mid;
}
}
return left;
}