力扣解题-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”,二分查找的关键在于精准定义区间和终止条件:
- 初始化区间:左边界
left=0,右边界right=x(平方根不可能超过x本身); - 二分循环条件:
left <= right(区间有效时继续查找); - 计算中间值:
mid = left + (right - left)/2(避免left+right溢出); - 判断中间值平方与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);
- 用
- 循环终止返回:循环结束时
left > right,此时right是最大的满足right² ≤ x的整数(即舍去小数的平方根)。
具体步骤(以示例2 x=8为例)
| 循环轮次 | left | right | mid | temp=mid² | 比较结果 | 边界调整 | 区间状态 |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 8 | 4 | 16 | 16>8 | right=3 | [0,3] |
| 2 | 0 | 3 | 1 | 1 | 1<8 | left=2 | [2,3] |
| 3 | 2 | 3 | 2 | 4 | 4<8 | left=3 | [3,3] |
| 4 | 3 | 3 | 3 | 9 | 9>8 | right=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)(仅使用几个指针变量,无额外空间);
- 优势:
- 效率远高于暴力遍历(暴力法O(x)),尤其适合大数值x;
- 原地操作,空间效率最优;
- 逻辑清晰,符合二分查找的经典范式,易理解和维护。
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;
}
核心逻辑说明
- 数学原理:求x的平方根等价于求解方程
n² = x,即f(n) = n² - x = 0,牛顿迭代法通过公式nₖ₊₁ = (nₖ + x/nₖ)/2不断逼近方程的根; - 迭代终止条件:当
res² ≤ x时,res即为最大的满足条件的整数; - 初始值选择:将初始值设为x,保证迭代过程单调递减且快速收敛;
- 溢出处理:使用
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;
}
核心逻辑说明
- 优化遍历范围:遍历到x/2即可,因为对于x≥2,平方根一定≤x/2(如x=8,x/2=4,平方根2≤4);
- 溢出避免:用
i <= x/i替代i*i <= x,避免int溢出; - 终止条件:找到第一个i,使得i≤x/i且i+1>x/(i+1),即i是最大的满足条件的整数。
性能说明
- 时间复杂度:O(x)(最坏情况x=2³¹-1,需遍历约10⁹次,会超时);
- 空间复杂度:O(1);
- 劣势:效率极低,仅适合x较小的场景,实际工程中不推荐使用。
总结
- 二分查找法(第一次解答):O(logx)时间+O(1)空间,逻辑经典、易理解,是本题的基础最优解;
- 牛顿迭代法:接近O(1)时间+O(1)空间,收敛更快,工程中更高效;
- 暴力遍历法:O(x)时间+O(1)空间,仅作理解参考,实际不推荐;
- 关键技巧:
- 核心思想:整数平方根的本质是“找最大的n满足n² ≤ x”;
- 溢出处理:必须用long存储平方值或改用除法判断,避免int溢出;
- 方法选择:优先选二分查找(易掌握)或牛顿迭代法(高效),拒绝暴力遍历。