Leetcode 69. Sqrt(x) Easy
https://leetcode.com/problems/sqrtx/
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.
分析:
leetcode上的这个不带精度要求,且输出一个整数即可(其实可以当成精度要求小于等于1)。
方法一:(二分法)
对于本题,最直观的方法就是二分法。使用二分法时,需要注意有三个指针,分别指向前中后(pre、medium、last,在书写、习惯、理解上,一般用left、mid、right进行表示,或者用start、mid、end)。此外,循环结束的条件也需要在书写程序之前想好,当然这也是本题的难点。
边界条件怎么确定呢?
假设left < right时循环,当left等于right时循环结束,并返回left或right,此时有left=right=mid。那么可以取一个整数进行实验:比如8。
第一次循环:
mid = (1 + 8)/2 = 4 (整数除法默认向下取整)
mid > 8/mid
right = mid - 1 = 3
left = 1
第二次循环:
mid = (1 + 3)/2 = 2
mid < 8/mid
left = mid + 1 = 3
right = 3
此时由于left == right 则跳出循环,返回的是3,但是这不应该是正确答案。如果在循环条件中加上等于,即left <= right,那么下一步还会有一次循环:
第三次循环:(新增的)
mid = 3
mid > 8/mid
right = mid - 1 = 2
left = 3
此时left > righ结束循环。写到这里,我们发现,如果在循环结束时,对于此例子,返回right是比较合适的。这是个例吗,还是都是这样?
比如x=10,那么mid的变化过程将是:5->2->3,此时10/3 == 3,直接返回了。再如x=11,那么mid变化过程将是:5->2->3,此时11/3 == 3,直接返回了。
再比如x=12,那么mid变化过程将是:6->3->4(此时left=4,right=5)->4(left=4,right=4)->此时right-1,变为left = 4, right = 3,返回right。
所以right必为返回值(在不存在mid == x / mid的条件下)
至于为什么,可以细想一下,我也没想明白,给出理论解释。直观感觉是开根号为向下取整,而返回right时,right < left,正好算是向下取整。
注意:之所以要写成除法的形式,是因为如果两个大数相乘,容易超内存。
int mySqrt(int x) {
int left = ; // left不能取0;因为如果x=1,那么(0+1)/2 = 0,导致mid等于0,做除法的时候会报错
int right = x;
while (left <= right) {
// int mid = (left + right) / 2; ——> 不要这么写,是因为如果right很大,left+right可能会超过整型最大值
int mid = left + (right - left) / 2;
if (mid == x / mid) {
return mid;
}
else if (mid > x / mid) {
right = mid - ;
}
else { // mid < x / mid
left = mid + ;
}
}
return right;
}
方法二:(牛顿法)
下面介绍牛顿法/牛顿迭代法。使用牛顿法千万不要死记硬背公式,要明白推导过程。牛顿法是用来求方程的近似根。通过使用f(x)的泰勒级数的前几项来寻找f(x)=0的根。(思考,和xgboost中使用牛顿法有什么区别)
关于泰勒级数的介绍:(说的不错)
https://baike.baidu.com/item/%E6%B3%B0%E5%8B%92%E7%BA%A7%E6%95%B0
https://zh.wikipedia.org/wiki/%E6%B3%B0%E5%8B%92%E7%BA%A7%E6%95%B0
关于牛顿法/牛顿迭代法:
https://baike.baidu.com/item/%E7%89%9B%E9%A1%BF%E8%BF%AD%E4%BB%A3%E6%B3%95 (百科介绍的太好了)
https://www.zhihu.com/question/20690553
https://www.cnblogs.com/wangkundentisy/p/8118007.html
(介绍了牛顿迭代法的使用,主要对问题进行求根转化后,然后求切线与x轴交点,并进行迭代更新此处更适合用来求根/零点,和下面这个优化算法是不太一样的)
https://blog.csdn.net/google19890102/article/details/41087931
(作为优化算法的牛顿法,整体思想就是利用迭代点处的一阶导数(梯度)和二阶导数(Hessen矩阵)对目标函数进行二次函数近似,然后把二次模型的极小点作为新的迭代点,并不断重复这一过程,直至求得满足精度的近似极小值。这就和XGBoost对损失函数进行二阶泰勒展开是相同的原理,且用目标函数对f_t-1(x)求一阶导数,所以说利用了牛顿法;而且在XGBoost中,求得的f_t-1(x)或者说w_q(x)即是下一次更新的叶子节点的权重。结合着论文和这篇博客还是比较容易懂,值得反复看)
为什么优化和求根都叫牛顿法呢?明明在求根和零点问题上,就是作切线嘛,明明没有泰勒展开啊,看看百度百科的“回答”(让我豁然开朗):
注意的关键词:非线性方程,泰勒级数,取线性部分,近似方程。
下面进行详细分析,推导出迭代关系式(用iPad手写推导过程),并给出代码:
总结:
由上分析我们可以发现,由牛顿法和切线法得来的递推公式是相同的,我们姑且可以认为切线法是牛顿法的几何表示。
此时,所谓的牛顿法对我已不再神秘,希望对你也一样如此!
int mySqrt(int x) {
long long ans = x;
while (ans * ans > x) { // 其实,我很疑惑:如果ans很大的情况下,ans*ans不应该会报错吗
ans = (ans + x / ans) / ; // 由公式化简得来
}
return ans;
}
至此,Leetcode上的这道 “Easy” 题目已经解决。但是往往在面试或实际问题中,要求最后得到的结果具备一定精度,即ans*ans - x < ε。此外,如果要求 ans值满足某一精度,我们就必须使用sqrt()求出其真实开根号值,然后作为判断条件。下面对一种进行代码书写,整体思路和不带精度相同,但是要注意需要把int型转为double型!
二分法:
double mySqrt(double x, double epsilon) {
double left = 1.0;
double right = x;
double mid = left + (right - left) / ;
while (fabs(mid * mid - x) > epsilon) { // 默认mid * mid不会超过范围,否则这道题就麻烦了
if (mid * mid > x) {
right = mid;
}
else if (mid * mid < x) {
left = mid;
}
else {
return mid;
}
mid = left + (right - left) / ;
}
return mid;
}
牛顿法:
double mySqrt(double x, double epsilon) {
double ans = x;
while (fabs(ans * ans - x) > epsilon) {
ans = (ans + x / ans) / ;
}
return ans;
}
注:
关于数学符号表示的知识,如:epsilon :ε