每日LeetCode : x 的算术平方根——从暴力求解到二分查找与牛顿迭代

95 阅读3分钟

问题描述

给定一个非负整数 x,请计算并返回它的 算术平方根(即 √x),结果只保留整数部分,小数部分将被舍去。
限制条件:不允许使用任何内置指数函数或运算符(如 Math.pow(x, 0.5)x ** 0.5)。


一、初步思路:暴力枚举法

思路说明

逐个尝试整数 i,直到找到满足 i * i > x 的最小整数,此时 i - 1 即为答案。

JavaScript 实现

function mySqrt(x) {
    if (x === 0 || x === 1) return x;
    let i = 1;
    while (i * i <= x) {
        i++;
    }
    return i - 1;
}

时间复杂度分析

  • 最坏情况下需要遍历到 √x,时间复杂度为 O(√x)
  • 对于大数(如 x = 2^31 - 1),效率较低。

二、优化方法一:二分查找法

核心思想

平方根的值一定在 0x 之间。通过 二分查找 缩小范围,每次将搜索区间减半,从而快速逼近答案。

JavaScript 实现

function mySqrt(x) {
    if (x < 2) return x;
    let left = 0, right = x;
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        // 使用 mid <= x / mid 避免整数溢出
        if (mid <= x / mid) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return right;
}

时间复杂度分析

  • 每次将搜索空间减半,时间复杂度为 O(log x)
  • 显著优于暴力法,适用于非常大的输入值。

优化点

  • 避免整数溢出:使用 mid <= x / mid 代替 mid * mid <= x
  • 边界处理:当 x < 2 时直接返回 x

三、进一步优化:牛顿迭代法

数学背景

牛顿迭代法是一种数值逼近方法,用于求解方程 f(x) = 0 的近似解。对于 x² = n,我们可以通过迭代公式逐步逼近 √n。

迭代公式推导

f(x) = x² - n,其导数为 f’(x) = 2x
根据牛顿迭代法的公式:

xk+1=12(xk+nxk)x_{k+1} = \frac{1}{2}(x_k + \frac{n}{x_k})

JavaScript 实现

function mySqrt(x) {
    if (x < 2) return x;
    let guess = x;
    while (true) {
        const newGuess = Math.floor((guess + x / guess) / 2);
        if (newGuess >= guess) {
            return guess;
        }
        guess = newGuess;
    }
}

时间复杂度分析

  • 牛顿法的收敛速度为 二次收敛,即每次迭代的误差平方级减少,因此在实践中非常高效。
  • 时间复杂度接近 O(log log x),远优于二分查找。

优势与局限性

  • 优势:收敛速度快,适合高精度计算。
  • 局限性:需要理解数学原理,且对初始值选择敏感(但此处固定初始值 x 已足够)。

五、方法对比与总结

方法时间复杂度空间复杂度适用场景优点缺点
暴力枚举法O(√x)O(1)小规模输入实现简单效率低
二分查找法O(log x)O(1)中等规模输入效率高,代码简洁需处理边界条件
牛顿迭代法O(log log x)O(1)大规模输入或高精度需求收敛速度快,效率极高需数学推导,实现稍复杂

推荐解法

  • 常规场景:优先使用 二分查找法,因其代码简洁、时间复杂度低。
  • 高性能需求:使用 牛顿迭代法,尤其适合对精度要求极高的场景(如科学计算)。

六、扩展思考

  1. 如何避免整数溢出
    在二分查找中,使用 mid <= x / mid 代替 mid * mid <= x,可避免大数相乘导致溢出。

  2. 如何处理浮点数精度问题
    牛顿迭代法中,使用 Math.floor((guess + x / guess) / 2) 代替浮点数除法,确保结果符合题目要求。

  3. 其他方法

    • 袖珍计算器算法:通过 Math.exp(0.5 * Math.log(x)) 计算平方根,但可能因浮点数精度损失导致错误,需额外修正。