1、题目描述
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
提示:
-
0 <= x <= 231 - 1
2、暴力破解
思路
- 从**
0
**开始遍历,依次计算i²
,直到找到第一个i
使得i² > x
。 - 返回**
i-1
**,因为(i-1)² ≤ x < i²
,即i-1
是x
的平方根的整数部分。
时间复杂度
- O(√x),最坏情况下需要遍历到
i ≈ √x
。
适用场景
- 优点:简单直接,适合小范围
x
。 - 缺点:当
x
很大时(如x=10^9
),效率低。
//暴力破解
static int bf(int x){
for (int i = 0; i < x; i++) {
if (i * i > x){
return i-1;
}
}
return -1;
}
3、二分法查找
思路
- 初始化搜索范围:
l = 0
,r = x
。 - 二分查找:
- 计算中间值
mid
。 - 如果
mid² ≤ x
,说明平方根可能更大,更新index = mid
并向右搜索(l = mid + 1
)。 - 如果
mid² > x
,说明平方根更小,向左搜索(r = mid - 1
)。
- 计算中间值
- 返回**
index
**,即最大的满足mid² ≤ x
的mid
。
时间复杂度
- O(log x),每次将搜索范围减半。
适用场景
- 优点:高效,适合大范围
x
(如x=10^18
)。 - 缺点:需要理解二分查找的边界条件。
//二分法,但是该方法如果x足够大的话,会超出Int类型的最大值,会mid * mid出现数值溢出,从而导致计算错误!
static int binarySearch(int x){
int index =-1;
int l=0,r=x;
while (l<=r){
int mid = l + (r-l)/2;
if (mid * mid <= x){
index = mid;
l = mid + 1;
}else {
r = mid - 1;
}
}
return index;
}
当测试用例**x = 2147395599
****,二分查找会出现错误。这是因为:**
- 数值溢出:
mid * mid
可能超过int
的最大值(2^31 - 1 = 2147483647
),导致计算错误。 - 边界条件:
x
的平方根是46340
(因为46340^2 = 2147395600
,刚好大于2147395599
),但某些实现可能返回46339
或错误结果。
修复后的二分查找
以下是修复后的二分查找实现,解决数值溢出问题:
修正思路
-
避免直接计算**
mid * mid
**:改用
mid <= x / mid
判断,防止mid * mid
溢出。 -
调整搜索范围:
初始化右边界
r = x
,但可以优化为r = x / 2 + 1
(因为sqrt(x) ≤ x/2 + 1
)。
//二分法改进:
static int newBinarySearch(int x) {
if (x == 0 || x == 1) return x; // 处理边界
int l = 1, r = x / 2 + 1; // 优化右边界
int ans = 0;
while (l <= r) {
int mid = l + (r - l) / 2;
if (mid <= x / mid) { // 避免 mid * mid 溢出
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return ans;
}
4、牛顿迭代
求x的平方根,可以转化为 x = n ^2,求n,=⇒ n = x / n
由数学推理可知,当 ( n + x/n) 的平均数要比n或者x/n 都要接近x的平方根,也就是(n + x/n ) /2 无限接近于 x的平方根。因此我们可以使用递归来无限次求 (n + x/n )/2 来无限接近,直到doubl的边界的时候,两者就是相等的时候。
思路
-
牛顿迭代公式:
通过迭代逼近平方根,公式为:
n = (n + x / n) /2
其中
x
是待求平方根的数,n
是当前近似值。 -
终止条件:当两次迭代结果几乎相同时(
res == i
),认为已收敛。 -
返回整数部分:最终结果强制转换为
int
。
时间复杂度
- O(log x),收敛速度极快,通常只需几次迭代。
适用场景
- 优点:数学上高效,适合需要高精度或大数计算。
- 缺点:涉及浮点数运算,可能因精度问题导致无限循环(需设置最大迭代次数)。
//牛顿迭代
static int newton(int x){
if (x == 0) return 0;
return (int)sqrt(x,x);
}
static double sqrt(double i, int x){
double res = (i + x/i) / 2;
if (res == i){
return i;
}else {
return sqrt(res, x);
}
}