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