题目描述
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
思路讲解
计算非负整数x
的算术平方根的整数部分,如果不使用任何内置的指数函数或算符,我们可以采用暴力解法(也称为直接法或枚举法)。这种方法的基本思路是从0开始逐个尝试,直到找到一个数,其平方不超过x
,并且这个数的下一个数的平方会大于x
。
int mySqrt(int x) {
if (x == 0) return 0; // 特殊情况处理
for (int i = 1; i <= x; ++i) {
if (i * i <= x && (i + 1) * (i + 1) > x) {
// 找到i,使得i*i <= x且(i+1)*(i+1) > x
// 直接返回i即可,因为题目要求只保留整数部分
return i;
}
}
// 理论上这行代码不会被执行到,因为上面的循环肯定会找到满足条件的i
// 但为了代码的完整性,还是加上这一行
return -1; // 返回一个不可能的值,表示出错(实际上这种情况不会发生)
}
但是,直接遍历从0到x
的所有数是非常低效的。为了计算非负整数x的算术平方根的整数部分,我们可以采用二分查找法。考虑到平方根的性质,我们可以设定一个查找范围从0到x(因为x的平方根肯定小于或等于x),并在这个范围内进行二分查找。
class Solution {
public:
int mySqrt(int x) {
if (x < 1) {
return 0;
}
int left = 1, right = x;
while (left < right) {
long long mid = left + (right - left + 1) / 2;
if (mid * mid <= x) {
left = mid;
} else if (mid * mid > x) {
right = mid - 1;
}
}
return left;
}
};
实现步骤:
- 边界条件检查:
- 首先,函数检查输入
x
是否小于 1。如果是,那么x
的平方根整数部分必然是 0(因为 0 和 1 的平方根整数部分都是 0),所以直接返回 0。
- 首先,函数检查输入
- 初始化二分查找的边界:
- 接着,函数初始化两个指针(或索引)
left
和right
,分别指向查找范围的左右边界。由于平方根的值至少为 0(对于非负整数x
),且最大不超过x
(因为sqrt(x) * sqrt(x) = x
),所以left
被初始化为 1(因为 0 的情况已经在边界条件中处理过了),right
被初始化为x
。
- 接着,函数初始化两个指针(或索引)
- 执行二分查找:
- 函数进入一个
while
循环,条件是left < right
。这个循环会一直执行,直到left
和right
相等,此时它们就指向了平方根的整数部分(或者比它大1的最小整数,但在接下来的步骤中会被正确处理)。 - 在每次循环中,函数计算中点
mid
。这里使用了long long
类型来存储mid
,以防止在计算mid * mid
时发生整数溢出。计算mid
时使用了(right - left + 1) / 2
,这实际上是一种“向上取整”的除法操作(在整数除法中),但它在这里的主要作用是确保当left
和right
相邻时,mid
会等于right
(从而避免无限循环)。 - 然后,函数检查
mid * mid
与x
的关系:- 如果
mid * mid <= x
,说明mid
可能是我们要找的平方根的整数部分(或者比它大),所以将left
更新为mid
,继续在右半部分查找。 - 如果
mid * mid > x
,说明mid
太大了,所以将right
更新为mid - 1
,在左半部分继续查找。
- 如果
- 函数进入一个
- 返回结果:
- 当
while
循环结束时,left
和right
相等,且它们指向了平方根的整数部分(因为我们在每次循环中都将范围缩小了,直到无法再缩小为止)。所以,函数返回left
(或right
,因为此时它们是相等的)作为结果。
- 当