Leetcode 69. Sqrt(x)及其擴展(有/無精度、二分法、牛頓法)詳解

Leetcode 69. Sqrt(x) Easyhtml

https://leetcode.com/problems/sqrtx/git

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:google

Input: 8
Output: 2
Explanation: The square root of 8 is 2.82842..., and since 
             the decimal part is truncated, 2 is returned.

分析:spa

leetcode上的這個不帶精度要求,且輸出一個整數便可(其實能夠當成精度要求小於等於1)。.net

方法一:(二分法)指針

對於本題,最直觀的方法就是二分法。使用二分法時,須要注意有三個指針,分別指向前中後(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

http://www.javashuo.com/article/p-waofaova-nt.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 :ε

相關文章
相關標籤/搜索