Loading

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}\right)^{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\right),\) 作一条斜率为 \(f^{\prime}\left(x_{i}\right)=2 x_{i}\) 的直线, 直线的方程为:

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

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

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

在进行 \(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;
}
posted @ 2021-04-05 20:19  反身而诚、  阅读(165)  评论(0编辑  收藏  举报