實現 int sqrt(int x)
函數。node
計算並返回 x 的平方根,其中 x 是非負整數。算法
因爲返回類型是整數,結果只保留整數的部分,小數部分將被捨去。函數
示例 1:測試
輸入: 4
輸出: 2
示例 2:優化
輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842...,
因爲返回類型是整數,小數部分將被捨去。
這道題很明顯不是讓咱們調用 Math.sqrt() 方法來計算,而是本身實現一個求平方根的算法。第一反應想到的方法是暴力循環求解!從 1 開始依次日後求平方數,當平方數等於 x 時,返回 i ;當平方數大於 x 時,返回 i - 1。spa
1 class Solution { 2 public int mySqrt(int x) { 3 for(int i = 0; i <= x; i++) { 4 if(i * i >= x){ 5 if(i * i == x) 6 return i; 7 else 8 return i - 1; 9 } 10 } 11 return 0; 12 } 13 }
這種方案經過了測試,可是成績慘不忍睹:code
執行用時 : 105 ms, 在Sqrt(x)的Java提交中擊敗了5.53% 的用戶
內存消耗 : 33.1 MB, 在Sqrt(x)的Java提交中擊敗了83.60% 的用戶
好奇心驅使下,我用了 Math.sqrt() 方法又提交了一遍答案:orm
1 class Solution { 2 public int mySqrt(int x) { 3 return (int)Math.sqrt(x); 4 } 5 }
成績有點無解:blog
執行用時 : 5 ms, 在Sqrt(x)的Java提交中擊敗了99.24% 的用戶
內存消耗 : 33.2 MB, 在Sqrt(x)的Java提交中擊敗了80.86% 的用戶
這樣一道乍看之下有點「蠢」的題目,其實有不少能夠深究的地方。Math.sqrt() 用的是什麼算法?求平方數的算法還有哪些?ip
Google 了一下「求平方根」,看到了兩個出鏡率最高的名詞,一個是咱們耳熟能詳的「二分法」,另外一個則是我第一次據說的「牛頓迭代法「。可貴五一假期有空,決定了解一下」牛頓迭代法「並本身寫出基於此算法的解題答案。
我是根據知乎上一個回答瞭解牛頓迭代法的,連接貼出來了,有興趣的朋友能夠移步去看一下。這裏簡單的經過他的文章說明一下思路。
這種算法的一個重要的思想是:切線是曲線的線性逼近。基於這種思想,牛頓嘗試用切線來研究曲線的問題,例如用切線的根近似的求出曲線的根。而後他觀察到一個現象,當在曲線上取某一點做切線時,以該切線的根做垂線,在垂線和曲線的交點處再做切線,以此循環往復,切線的根逐漸會逼近曲線的根。如圖所示(A點時第一個取的點)。
固然,其實這種迭代並非必定能保證會向曲線的根逼近,具體緣由能夠移步上述連接。可是求二次方程的根是沒有問題的。
迴歸到題目,求 a 的平方根,實際上能夠轉換成求二次方程 x^2 - a = 0 的解的問題。而後能夠做出該二次方程的曲線,經過迭代逼近曲線 y = 0 處 x 的值,該 x 便是須要求得的答案。提現到程序中以下:
1 class Solution { 2 public int mySqrt(int x) { 3 if(x == 1) 4 return 1; 5 double _x = x >> 1; 6 double _y = _x * _x - x; 7 double a = (-_y + 2 * _x * _x) / 2 / _x; 8 while(_y > 0.1 || _y < -0.1){ 9 _x = a; 10 _y = _x * _x - x; 11 a = (-_y + 2 * _x * _x) / 2 / _x; 12 } 13 return (int)_x; 14 } 15 }
這個程序很直觀的反應了迭代的過程,"_x" 是二次方程的橫座標,"_y" 是方程的縱座標,"a" 是切線與 x 軸的交點處的橫座標。選取的第一點爲 x / 2 做爲橫座標,當 _y 的值逼近 0 的時候,返回 _x。該方法的成績很接近 Math.sqrt(),結果爲:
執行用時 : 6 ms, 在Sqrt(x)的Java提交中擊敗了92.91% 的用戶
內存消耗 : 33.7 MB, 在Sqrt(x)的Java提交中擊敗了75.11% 的用戶
這裏其實能夠注意到,該二次方程必定是關於 y 軸對稱的,並且二次方程在迭代過程當中,若初始點在根的右邊,則迭代的點會一直出如今根的右邊,且一直逼近根。咱們要找的實際上是比根小的最大的整數,能夠把 a 換成 int 類型,在逼近過程當中,當 a 第一次小於等於 x / a 時,返回 a。
對程序進行簡化,去掉一些沒必要要的參數,優化最後判斷足夠逼近的方式,最後程序爲:
1 class Solution { 2 public int mySqrt(int x) { 3 if(x == 1 || x == 0) 4 return x; 5 int a = x >> 1; 6 while(a > x / a){ 7 a = (a + x/a) / 2; 8 } 9 return a; 10 } 11 }