LeetCode 69 題

1.題目要求

實現 int sqrt(int x) 函數。node

計算並返回 x 的平方根,其中 x 是非負整數。算法

因爲返回類型是整數,結果只保留整數的部分,小數部分將被捨去。函數

示例 1:測試

輸入: 4
輸出: 2

示例 2:優化

輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842..., 
     因爲返回類型是整數,小數部分將被捨去。

2.初次嘗試

這道題很明顯不是讓咱們調用 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 了一下「求平方根」,看到了兩個出鏡率最高的名詞,一個是咱們耳熟能詳的「二分法」,另外一個則是我第一次據說的「牛頓迭代法「。可貴五一假期有空,決定了解一下」牛頓迭代法「並本身寫出基於此算法的解題答案。

3.牛頓迭代法

我是根據知乎上一個回答瞭解牛頓迭代法的,連接貼出來了,有興趣的朋友能夠移步去看一下。這裏簡單的經過他的文章說明一下思路。

如何通俗易懂地講解牛頓迭代法求開方?數值分析?​www.zhihu.com

這種算法的一個重要的思想是:切線是曲線的線性逼近。基於這種思想,牛頓嘗試用切線來研究曲線的問題,例如用切線的根近似的求出曲線的根。而後他觀察到一個現象,當在曲線上取某一點做切線時,以該切線的根做垂線,在垂線和曲線的交點處再做切線,以此循環往復,切線的根逐漸會逼近曲線的根。如圖所示(A點時第一個取的點)。

固然,其實這種迭代並非必定能保證會向曲線的根逼近,具體緣由能夠移步上述連接。可是求二次方程的根是沒有問題的。

4.牛頓迭代法求平方根

迴歸到題目,求 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% 的用戶

5.簡化

這裏其實能夠注意到,該二次方程必定是關於 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 }
相關文章
相關標籤/搜索