• Easy | LeetCode 69. x 的平方根 | 袖珍计算器 | 二分法 | 牛顿迭代法


    69. x 的平方根

    实现 int sqrt(int x) 函数。

    计算并返回 x 的平方根,其中 x 是非负整数。

    由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

    示例 1:

    输入: 4
    输出: 2
    

    示例 2:

    输入: 8
    输出: 2
    说明: 8 的平方根是 2.82842..., 
         由于返回类型是整数,小数部分将被舍去。
    

    解题思路

    本题应该还加一个要求, 不允许直接开根号得出结果

    方法一: 袖珍计算器算法

    [sqrt{x}=x^{1 / 2}=left(e^{ln x} ight)^{1 / 2}=e^{frac{1}{2} ln x} ]

    根据以上公式, 可以替代直接根号的运算。但是这样计算的结果, 可能会带有误差。有精度丢失导致值偏小的可能性。所以在返回结果时, 需要把ans+1拿来尝试一次。

    public int mySqrt(int x) {
        if (x == 0) {
            return 0;
        }
        int ans = (int) Math.exp(0.5 * Math.log(x));
        return (long) (ans + 1) * (ans + 1) <= x ? ans + 1 : ans;
    }
    

    方法二: 二分法

    找整数x, x+1,使得x ^ 2 <= n 并且(x+1)^2 > n。可使用二分的方法来查找。二分的初始下界是0, 初始上界是N。

    public int mySqrt(int x) {
        int l = 0, r = x, ans = -1;
        while (l <= r) {
            int mid = l + (r - l) / 2;
            if ((long) mid * mid <= x) {
                ans = mid;
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return ans;
    }
    

    方法三: 牛顿迭代法

    如图, 不断得用切线与X轴的交点为基础做切线迭代, 用于X轴的交点不断去逼近结果。

    具体的公式推导如下:

    我们选择 (x 0=C) 作为初始值。
    在每一步迭代中, 我们通过当前的交点 (x_{i},) 找到函数图像上的点 (left(x_{i}, x_{2}^{2}-C ight),) 作一条斜率为 (f^{prime}left(x_{i} ight)=2 x_{i}) 的直线, 直线的方程为:

    [egin{aligned} y l &=2 x ileft(x-x_{i} ight)+x_{2}^{2}-C \ &=2 x i x-left(x_{i}^{2}+C ight) end{aligned} ]

    与横轴的交点为方程 (2 x i x-left(x_{2}^{2}+C ight)=0) 的解, 即为新的迭代结果 (x_{i+1}:)

    [x_{i+1}=frac{1}{2}left(x_{i}+frac{C}{x_{i}} ight) ]

    在进行 (k) 次迭代后, (x_{k}) 的值与真实的零点 (sqrt{C}) 足田接近, 即可作为答案。

    这种方法有几点需要明白:

    为什么选择 (x 0=C) 作为初始值?

    • 因为 (y=x^{2}-C) 有两个零点 (-sqrt{C})(sqrt{C}) 。如果我们取的初始值较小,可能会迭代到 (-sqrt{C}) 这 个零点, 而我们希望找到的是 (sqrt{C}) 这个零点。因此选择 (x 0=C) 作为初始值,每次迭代均有 (x_{i+1}<x_{i},) 零点 (sqrt{C}) 在其左侧, 所以我们一定会迭代到这个零点。
    • 迭代到何时才算结束?
      每一次迭代后, 我们都会距离零点更进一步,所以当相邻两次迭代得到的交点非常接近时, 我们 就可以断定, 此时的结果已经足够我们得到答案了。一般来说, 可以判断相邻两次迭代的结果的 差值是否小于一个极小的非负数 (epsilon,) 其中 (epsilon) 一般可以取 (10^{-6})(10^{-7})
    • 如何通过迭代得到的近似零点得出最终的答案?
      由于 (y=f(x))([sqrt{C},+infty]) 上是凸函数 (convex function) 且恒大于等于零,那么只要我们选取 的初始值 (x 0) 大于等于 (sqrt{C},) 每次迭代得到的结果 (x_{i}) 都会恒大于等于 (sqrt{C}) 。因此只要 (epsilon) 选择地足 句小, 最终的结果 (x k) 只会稍稍大于真正的零点 (sqrt{C}) 。在题目给出的 32 位整数范围内,不会出现 下面的情况:
      真正的零点为 (n-1 / 2 epsilon,) 其中 (n) 是一个正整数, 而我们迭代得到的结果为 (n+1 / 2 epsilon_{circ}) 在对结 果保留整数部分后得到 (n,) 但正确的结果为 (n-1_{0})
    public int mySqrt(int x) {
        if (x == 0) {
            return 0;
        }
    
        double C = x, x0 = x;
        while (true) {
            double xi = 0.5 * (x0 + C / x0);
            if (Math.abs(x0 - xi) < 1e-7) {
                break;
            }
            x0 = xi;
        }
        return (int) x0;
    }
    
  • 相关阅读:
    近期学习(3)
    近期学习(1)
    近期学习(2)
    今日练习
    《明朝那些事儿》
    记一次针对恶意攻击者的渗透测试
    Kali Linux使用问题记录
    MySQL floor()报错原理
    使用复合设计模式扩展持久化的CURD,Select能力
    c#/js代码命名规范及代码规范
  • 原文地址:https://www.cnblogs.com/chenrj97/p/14619330.html
Copyright © 2020-2023  润新知