Leetcode: Sqrt(x)

Implement int sqrt(int x).

难度:76,用二分查找。要求是知道结果的范围,取定左界和右界,然后每次砍掉不满足条件的一半,知道左界和右界相遇。算法的时间复杂度是O(logx),空间复杂度是O(1)。

 1 public class Solution {
 2     public int sqrt(int x) {
 3         if (x < 0) {
 4             return -1;
 5         }
 6         if (x == 0) {
 7             return 0;
 8         }
 9         int l = 1;
10         int r = x/2 + 1;
11         while (l <= r) {
12             int m = (l + r)/2;
13             if (m <= x/m && x/(m+1) < m+1) return m;
14             else if (m > x/m) {
15                 r = m - 1;
16             }
17             else {
18                 l = m + 1;
19             }
20         }
21         return -1;
22     }
23 }

 其实这道题还是很tricky的,上面这行代码13行这么做,而不写成m*m <= x 其实是有深意的,是为了防止m*m的溢出。但是有人会攻击说m <= x/m && x/(m+1) < m+1是不是 m*m <= x && (m+1)*(m+1) > x的等价有效替换呢? 这个有待商榷。

而且因为用除来解决overflow的问题,新的问题便被引入,那就是denominator为0的问题。为此,x=0的情况要单独考虑(否则m=0会在被除数位置)。左边界设置为1,右边界设置为x/2+1(向上取整以确保l, r之间包含target,否则比如x=1, l=1, r=0; x=2, l=1, r=1; 这些都漏掉了)

因此下面这个做法还是使用乘法,但是为了防止溢出,使用了Longlong型, 它的返回条件如第8行所示,并没有采取我做的方式即 m*m<= x && (m+1)*(m+1)>x , 它这样做是利用到了左右两个边界相遇之后左边界会停在比target大的整数处,而右边界会停在比target小的整数处 

 1 public class Solution {
 2     int sqrt(int x) {
 3         int i = 0;
 4         int j = x / 2 + 1;
 5         while (i <= j)
 6         {
 7             int mid = (i + j) / 2;
 8             long sq = (long)mid * mid;
 9             if (sq == x) return mid;
10             else if (sq < x) i = mid + 1;
11             else j = mid - 1;
12         }
13         return j;
14     }
15 }

2. 牛顿迭代法

另外,有牛顿法的解法,参见http://www.cnblogs.com/AnnieKim/archive/2013/04/18/3028607.html,牛顿法同时也可以解结果是double的情况

   为了方便理解,就先以本题为例:

   计算x2 = n的解,令f(x)=x2-n,相当于求解f(x)=0的解,如左图所示。

   首先取x0,如果x0不是解,做一个经过(x0,f(x0))这个点的切线(tangent line),与x轴的交点为x1

   同样的道理,如果x1不是解,做一个经过(x1,f(x1))这个点的切线,与x轴的交点为x2

   以此类推。

   以这样的方式得到的xi会无限趋近于f(x)=0的解。

   判断xi是否是f(x)=0的解有两种方法:

   一是直接计算f(xi)的值判断是否为0,二是判断前后两个解xi和xi-1是否无限接近。

经过(xi, f(xi))这个点的切线方程为f(x) = f(xi) + f’(xi)(x - xi),其中f'(x)为f(x)的导数,本题中为2x。令切线方程等于0,即可求出xi+1=xi - f(xi) / f'(xi)。

继续化简,xi+1=xi - (xi- n) / (2xi) = xi - xi / 2 + n / (2xi) = xi / 2 + n / 2xi = (xi + n/xi) / 2。

举个例子, 为了求sqrt(n),就是求:f(x) = x^2 - n当f(x) = 0的解

令y = x^2 - n,

取一点(x1, y1), 其切线方程是y-y1 = 2x1*(x - x1), where 2x1是(x1,y1)处导数

y - (x1^2 - n) = 2x1*(x - x1)

y = 2x1*x - x1^2 - n

令y取零, 0 = 2x1*x - x1^2 - n

x2 <-------- 解得 x = x1/2 + n/2x1 

有了迭代公式,程序就好写了。

输入输出都是int型:

 1 public class Newton {
 2     public int sqrt(int x) {
 3             if (x == 0) return 0;
 4             double last = 0;
 5             double res = 1;
 6             while (res != last)
 7             {
 8                 last = res;
 9                 res = (res + x / res) / 2;
10             }
11             return (int)res;
12         
13     }
14     
15     public static void main(String[] args){
16         Newton newton = new Newton();
17         System.out.println(newton.sqrt(20));
18     }
19 }

输入输出都是double型,或者输入int, 输出double:

 1 public class Newton {
 2     public double sqrt(int x) {
 3             if (x == 0) return 0;
 4             double last = 0;
 5             double res = 1;
 6             while (res != last)
 7             {
 8                 last = res;
 9                 res = (res + x / res) / 2;
10             }
11             return res;
12         
13     }
14     
15     public static void main(String[] args){
16         Newton newton = new Newton();
17         System.out.println(newton.sqrt(3));
18     }
19 }
原文地址:https://www.cnblogs.com/EdwardLiu/p/3981176.html