力扣解题-69. x 的平方根

3 阅读6分钟

力扣解题-69. x 的平方根

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

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

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4

输出:2

示例 2:

输入:x = 8

输出:2

解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

提示:

0 <= x <= 2³¹ - 1

Related Topics

数学、二分查找


第一次解答

解题思路

核心方法:二分查找法(整数平方根最优解),利用“非负整数的平方根一定在[0, x]区间内”的特性,通过二分法缩小查找范围,避免暴力遍历,时间复杂度O(logx),空间复杂度O(1),是本题的最优解法。

核心逻辑拆解

求整数平方根的核心是“找最大的整数n,使得n² ≤ x”,二分查找的关键在于精准定义区间和终止条件:

  1. 初始化区间:左边界left=0,右边界right=x(平方根不可能超过x本身);
  2. 二分循环条件left <= right(区间有效时继续查找);
  3. 计算中间值mid = left + (right - left)/2(避免left+right溢出);
  4. 判断中间值平方与x的关系
    • long temp = (long)mid * mid存储平方值(避免int类型溢出,如mid=46340时,mid²=2147395600,超过int最大值2147483647);
    • temp == x:mid就是精确平方根,直接返回;
    • temp < x:说明mid偏小,需增大左边界(left=mid+1);
    • temp > x:说明mid偏大,需减小右边界(right=mid-1);
  5. 循环终止返回:循环结束时left > right,此时right是最大的满足right² ≤ x的整数(即舍去小数的平方根)。
具体步骤(以示例2 x=8为例)
循环轮次leftrightmidtemp=mid²比较结果边界调整区间状态
10841616>8right=3[0,3]
203111<8left=2[2,3]
323244<8left=3[3,3]
433399>8right=2[3,2](终止)
循环终止,返回right=2,符合示例2的结果。
关键细节说明
  • 避免溢出:必须将mid*mid强转为long类型,否则当x接近2³¹-1时,mid的平方会超出int范围导致计算错误;
  • 终止返回right:循环结束时left > right,right是最后一个满足right² ≤ x的数,例如x=8时,right=2(2²=4≤8),left=3(3²=9>8);
  • 边界处理:x=0时,left=0、right=0,mid=0,直接返回0;x=1时,mid=1,temp=1==1,返回1。
性能说明
  • 时间复杂度:O(logx)(二分查找的次数为以2为底x的对数,如x=2³¹-1时,仅需约31次循环);
  • 空间复杂度:O(1)(仅使用几个指针变量,无额外空间);
  • 优势:
    1. 效率远高于暴力遍历(暴力法O(x)),尤其适合大数值x;
    2. 原地操作,空间效率最优;
    3. 逻辑清晰,符合二分查找的经典范式,易理解和维护。
    public int mySqrt(int x) {
        int left=0;
        int right=x;
        while(left<=right){
            int mid=left+(right-left)/2;
            long temp=(long)mid*mid;
            if(temp==x){
                return mid;
            }else if(temp<x){
                left=mid+1;
            }else {
                right=mid-1;
            }
        }
        return right;
    }

示例解答

解题思路

解法1:牛顿迭代法(收敛更快,进阶解法)

核心方法:牛顿迭代法,利用数学迭代公式逼近平方根,收敛速度比二分查找更快(通常只需几次迭代),时间复杂度接近O(1),空间复杂度O(1),是工程中更高效的解法。

代码实现
public int mySqrt(int x) {
    if (x == 0) {
        return 0;
    }
    // 牛顿迭代初始值设为x,避免初始值过小导致收敛慢
    long res = x;
    // 迭代公式:res = (res + x/res) / 2
    while (res * res > x) {
        res = (res + x / res) / 2;
    }
    return (int) res;
}
核心逻辑说明
  1. 数学原理:求x的平方根等价于求解方程n² = x,即f(n) = n² - x = 0,牛顿迭代法通过公式nₖ₊₁ = (nₖ + x/nₖ)/2不断逼近方程的根;
  2. 迭代终止条件:当res² ≤ x时,res即为最大的满足条件的整数;
  3. 初始值选择:将初始值设为x,保证迭代过程单调递减且快速收敛;
  4. 溢出处理:使用long类型存储res,避免乘法溢出。
执行流程(以x=8为例)
  • 初始res=8:8²=64>8 → res=(8+8/8)/2=(8+1)/2=4;
  • res=4:4²=16>8 → res=(4+8/4)/2=(4+2)/2=3;
  • res=3:3²=9>8 → res=(3+8/3)/2=(3+2)/2=2;
  • res=2:2²=4≤8 → 终止迭代,返回2。
优势说明
  • 收敛速度极快:对于x=2³¹-1,仅需约5次迭代即可收敛,比二分查找的31次循环快得多;
  • 代码更简洁:无需处理复杂的二分边界,逻辑更简洁;
  • 工程实用性强:是各大数学库中平方根计算的底层实现方式。
解法2:暴力遍历法(直观但低效,仅作对比)

核心方法:暴力遍历法,从0开始逐个尝试,直到找到最大的n使得n² ≤ x,逻辑最直观但效率极低,仅适合理解问题本质。

代码实现
public int mySqrt(int x) {
    if (x == 0 || x == 1) {
        return x;
    }
    int res = 0;
    // 遍历到x/2即可,因为当n>x/2时,n²>x(x≥2)
    for (int i = 1; i <= x / 2; i++) {
        // 避免i*i溢出,改用除法判断
        if (i <= x / i && (i + 1) > x / (i + 1)) {
            res = i;
            break;
        }
    }
    return res;
}
核心逻辑说明
  1. 优化遍历范围:遍历到x/2即可,因为对于x≥2,平方根一定≤x/2(如x=8,x/2=4,平方根2≤4);
  2. 溢出避免:用i <= x/i替代i*i <= x,避免int溢出;
  3. 终止条件:找到第一个i,使得i≤x/i且i+1>x/(i+1),即i是最大的满足条件的整数。
性能说明
  • 时间复杂度:O(x)(最坏情况x=2³¹-1,需遍历约10⁹次,会超时);
  • 空间复杂度:O(1);
  • 劣势:效率极低,仅适合x较小的场景,实际工程中不推荐使用。

总结

  1. 二分查找法(第一次解答):O(logx)时间+O(1)空间,逻辑经典、易理解,是本题的基础最优解;
  2. 牛顿迭代法:接近O(1)时间+O(1)空间,收敛更快,工程中更高效;
  3. 暴力遍历法:O(x)时间+O(1)空间,仅作理解参考,实际不推荐;
  4. 关键技巧
    • 核心思想:整数平方根的本质是“找最大的n满足n² ≤ x”;
    • 溢出处理:必须用long存储平方值或改用除法判断,避免int溢出;
    • 方法选择:优先选二分查找(易掌握)或牛顿迭代法(高效),拒绝暴力遍历。