Leetcode二分查找(5)
69. x 的平方根
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4
输出:2
示例 2:
输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。
解题思路总览
- 二分查找(搜索平方 <= x 的最大整数)
- 牛顿迭代法(Newton / Heron)
- 位运算按位试商法(Binary Digit / Bit-by-Bit method)
- 连续减去奇数法(数学性质,对比用,最坏 O(√x))
- 线性扫描或暴力枚举(仅用于错误示例/对比)
满足题目 O(log x) 要求的:1(二分)、2(牛顿平均情况下很快,迭代次数 ~ O(log log x) 数值方法)、3(固定 ~ 16 或 32 次迭代)。方法4、5 不满足最坏 O(log x)(示例展示思路差异)。
思路一:二分查找
原理与适用场景:
我们寻找最大整数 r 使得 r^2 <= x。答案一定在 [0, x](更紧的上界可用 min(x, 46340) 对 int 而言;但直接用 x 也安全)。用二分在 [0, x] 中查找:若 mid^2 <= x 则 mid 可能是答案,移动左边界;否则收缩右边界。注意 mid*mid 可能溢出,需要用 long。
适合任何需要在单调函数上取边界的场景,是最直接稳健模板。
实现步骤:
- 特判 x < 2 直接返回 x。
- l=1, r=x/2(因为平方根不会超过 x/2 当 x>=2;也可用 x 但更宽)。
- while(l <= r) 取 mid。
- 若 mid^2 == x 返回 mid。
- 若 mid^2 < x 记录 ans=mid, l=mid+1。
- 否则 r=mid-1。
- 返回 ans。
JAVA 代码实现:
public class SqrtBinary {public int mySqrt(int x) {if (x < 2) { // 0 和 1 的平方根就是自身return x; // 直接返回}int l = 1; // 左边界(1 开始)int r = x / 2; // 右边界(平方根不会超过 x/2 当 x>=2)int ans = 1; // 记录满足条件的最大值while (l <= r) { // 闭区间二分int mid = l + (r - l) / 2; // 取中long sq = (long) mid * mid; // 转 long 防溢出if (sq == x) { // 刚好平方等于 xreturn mid; // 直接返回} else if (sq < x) { // mid 仍然可行,尝试更大ans = mid; // 更新答案l = mid + 1; // 向右侧继续} else { // sq > x 需要缩小r = mid - 1; // 收缩右边界}}return ans; // 返回平方 <= x 的最大整数}
}
思路二:牛顿迭代法(Newton / Heron)
原理与适用场景:
求解方程 f(y)=y^2 - x = 0。Newton 迭代公式:y_{k+1} = (y_k + x / y_k) / 2。当 y_k 收敛后即为 sqrt(x)。由于我们要的是整数部分,可迭代到 y_{k+1} >= y_k 或两次迭代差值 < 1 时停止,然后向下取整。牛顿法收敛二次速度很快,迭代次数极少(对 32 位整数一般 < 8 次)。
需要注意:使用 long 避免 x / y 溢出;终止条件谨慎设置防止死循环。
实现步骤:
- x < 2 返回 x。
- 取初始估计 y = x(或 x/2+1 更紧)。
- while (y*y > x) 更新 y = (y + x / y)/2。
- 循环结束 y 即 floor(sqrt(x))。
JAVA 代码实现:
public class SqrtNewton {public int mySqrt(int x) {if (x < 2) { // 0/1 直接返回return x; // 返回本身}long y = x; // 初始估计(long 防溢出)while (y * y > x) { // 还未收敛到平方 <= xy = (y + x / y) / 2; // Newton 迭代公式}return (int) y; // y 已经是整数部分}
}
思路三:位运算按位试商法(逐位构造平方根)
原理与适用场景:
从结果的最高可能位开始尝试设置,若设置该位后 candidate^2 <= x 则保留该位,否则清除。与“二进制开方”或“构造数字”类似。32 位整数的平方根最多 16 位(因为 2^16=65536 > sqrt(Integer.MAX_VALUE) ≈ 46340),因此迭代 16~32 次即可。
该方法时间 O(log x),常数小,完全整数运算,适合对位运算有要求或在无除法环境下(可用加法和移位判断平方)略作改造。
实现步骤:
- ans=0。
- 选择最高试探位 bit = 1<<15(因为 2^15=32768;再高一位 1<<16=65536 平方已超过 int 最大 sqrt 范围)。亦可从 1<<16 开始检测安全范围。
- 从高到低:
- temp = ans | bit。
- 若 (long)temp * temp <= x,则 ans = temp。
- bit >>= 1。
- 返回 ans。
JAVA 代码实现:
public class SqrtBitwise {public int mySqrt(int x) {if (x < 2) { // 小数特判return x; // 直接返回}int ans = 0; // 当前构造的结果int bit = 1 << 15; // 从第 15 位开始尝试(安全上界)while (bit > 0) { // 逐位下降int candidate = ans | bit; // 尝试把该位设为 1long sq = (long) candidate * candidate; // 计算平方if (sq <= x) { // 若平方仍不超过 xans = candidate; // 接受该位}bit >>= 1; // 继续下一位}return ans; // 已构造出最大平方 <= x 的值}
}
思路四:连续减去奇数法(数学性质,对比)
原理与适用场景:
利用 1 + 3 + 5 + … + (2n-1) = n^2。不断从 x 中依次减去递增奇数,能减多少次 n 即 floor(sqrt(x))。但需要 O(√x) 次循环,无法满足 O(log x) 要求,仅作数学性质展示或当 x 很小(例如 < 10^6)且代码极简的情况。
实现步骤:
- count=0, odd=1。
- while x >= odd: x -= odd; odd += 2; count++。
- 返回 count。
JAVA 代码实现(不推荐在大输入使用):
public class SqrtSubtractOdd { // O(√x) 仅示意public int mySqrt(int x) {int count = 0; // 已经减去的奇数个数int odd = 1; // 当前奇数while (x >= odd) { // 还能继续减x -= odd; // 减去当前奇数odd += 2; // 下一个奇数count++; // 次数加 1}return count; // count 即平方根整数部分}
}
思路五:线性扫描 / 暴力枚举(对比:最坏 O(√x) 甚至 O(x))
原理与适用场景:
从 0/1 开始逐个 i 检查 i^2 <= x。若 i^2 > x 则 i-1 为答案。显然最坏 O(√x),大数性能差。仅用于展示与二分/牛顿的差距。
实现步骤:
- i=0 while ( (long)(i+1)^2 <= x ) i++。
- 返回 i。
(代码略)
补充说明(对比分析)
-
复杂度对比:
- 二分:时间 O(log x),空间 O(1)。
- 牛顿:时间 O(log log x) 级迭代(实际常数极小),空间 O(1)。
- 位试商:时间 O(log x)(固定常数循环),空间 O(1)。
- 减奇数:时间 O(√x),空间 O(1)。
- 暴力:时间 O(√x)(若从 0 到 √x),空间 O(1)。
-
精度与正确性:牛顿、二分、位试商都能得到精确整数部分;需要注意牛顿法结束条件,用 while(y*y > x) 是常用且安全的整数版写法。
-
溢出风险:
- midmid、candidatecandidate 使用 long。
- 牛顿中 y, x / y 均用 long。
-
推荐优先级:
- 若强调模板清晰:二分。
- 若追求极少迭代:牛顿。
- 若对位运算感兴趣或限制除法:位试商。
-
测试用例建议:
- x=0 -> 0
- x=1 -> 1
- x=4 -> 2
- x=8 -> 2
- x=15 -> 3
- x=2147395599 (46339^2) -> 46339
- x=2147483647 (INT_MAX) -> 46340
-
常见错误:
- 忘记 long 导致乘法溢出得到负数。
- 牛顿法用 double 造成精度向上取整,需最后强制向下取整。
- 二分上界使用 x/2 时未处理 x=2,3 等小值(已在特判覆盖)。
-
总结:
- 本题最易维护方案:二分。
- 高效方案:牛顿(写法更短)。
- 特殊环境:位运算试商法。
综上,推荐掌握至少二分与牛顿两种,以便在不同语境下快速实现整数平方根。