@Leetcodex的平方根
简单的数学问题,这并不是一道考验思路的题,而是考验算法运用的问题,怎么更快的解决才是王道!请看题干:
实现 int sqrt(int x) 函数。计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去。
首先我们第一份代码给到笔者投机取巧通过的一份代码,请看:
class Solution {
public:
int mySqrt(int x) {
long long ans;
double t=sqrt(x);
ans=(long long)t;
return ans;
}
};
哇塞,直接把要实现的函数给用进了程序里面,再来一个强制类型转换,居然搞定了。
其实不用强制转成int应该也能通过,但是为了尊重一下题干,笔者觉得还是写一下为好。
那么言归正传,本题的正宗思想是什么?要找到一个确定肯定以及绝对存在的答案,为什么不直接使用查找算法呢?于是笔者来了一套二分查找,请看代码:
class Solution {
public:
int mySqrt(int x) {
long long m=0;
long long n=x/2+1;
long long ans=0;
while(m<=n){
long long mid=(m+n)/2;
ans=mid*mid;
if(ans==x)
return mid;
else if(ans<x)
m=mid+1;
else
n=mid-1;
}
return n;
}
};
这份代码看似简单,其实暗藏玄机。由于使用int类型可能无法存下某些数据,故干脆直接掏出long long来的简单。定义头尾中间,最终结果是ans,将其与x比较,如果小了就左端变成mid加一,如果大了就右端变成mid减一,判断简单又好用,人见人爱。
那么如何解决要取整的问题呢,比如示例2中的情况,其中8的平方根并不是一个整数。不用慌,其实这个问题已经不小心解决了。由于循环是执行到m<=n这个情况不满足为止,此时说明n经过最后一次减一之后已经小于m了,说明减一之前的n值和m值都不满足,并且是过大,而m一路遍历过来发现目前的n值是偏小,所以此时的这个偏小的n值正是取整之后得到的答案,直接输出即可通过。
但是真正的大佬早就看不上二分法了,牛顿迭代法才是真正最最高端的解法,请看代码:
class Solution {
public:
int mySqrt(int x) {
long long ans=x/2+1;
while (ans*ans>x)
ans=(ans+x/ans)/2;
return ans;
}
};
牛顿解法公式一目了然:ans=(ans+x/ans)/2 代码写完甚至有些意犹未尽,然而问题已经解决了。
所以这个方法到底是怎么回事?牛顿迭代法又称为牛顿-拉夫逊(拉弗森)方法,是一个快速寻找平方根的解法,具体可以参考两位大佬的blog
https://blog.csdn.net/ccnt_2012/article/details/81837154,以及https://www.cnblogs.com/qlky/p/7735145.html
里面有对其详细的分析过程,在这里我挑挑重点谈一谈:
要计算x² = n的解,令f(x)=x²-n,相当于求f(x)=0的解,画出坐标图像,取x0,若x0不是解,做一个经过(x0,f(x0))点的切线,与x轴的交点为x1,再判断x1是否是解,如果x1也不是解,做一个经过(x1,f(x1))点的切线,与x轴的交点为x2,以此类推,可以得到无限趋近于f(x)=0的解xi。
对xi的判断有两种方法,一是直接计算f(xi)的值是否为0,二是判断前后两个解xi和xi-1是否无限接近。经过(xi, f(xi))这个点的切线方程为f(x) = f(xi) + f’(xi)(x - xi),其中f’(x)为导数,即2x。令切线方程等于0,得xi+1=xi - f(xi) / f’(xi),继续化简得 xi+1=xi - (xi² - n) / (2xi) = xi - xi / 2 + n / (2xi) = xi / 2 + n / 2xi = (xi + n/xi) / 2。
那么实际应用到本题之中,这个最先取得的x0当然就是x/2+1这个值,从这个值往前进行判断,这种判断方法能避免之前二分查找时的挨个判断的低效率,但是所耗时间也不会因为这个减少特别多,依据之前的公式推断即可得到一个最为借鉴的值,最后一个数的判断与二分查找的判断原理类似,循环判断条件是ans*ans>x,所以也是能直接得到正确答案。