Leetcode 69. Sqrt(x)及其扩展(有/无精度、二分法、牛顿法)详解

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 = 1;  // 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 - 1;
        }
        else {  // mid < x / mid
            left = mid + 1;
        }
    }
    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) / 2; // 由公式化简得来
    }
    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) / 2;
    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) / 2;
    }
    return mid;
}

牛顿法:

double mySqrt(double x, double epsilon) {
    double ans = x;
    while (fabs(ans * ans - x) > epsilon) {
        ans = (ans + x / ans) / 2;
    }
    return ans;
}

 

注:

关于数学符号表示的知识,如:epsilon :ε

 

posted @ 2019-09-22 20:51  FlashX  阅读(501)  评论(0编辑  收藏  举报