题目描述
给你一个非负整数 x,计算并返回 x 的算术平方根。由于返回类型是整数,结果只保留整数部分,小数部分将被舍去。不得使用任何内置指数函数和算符,例如 pow(x, 0.5) 或 x ** 0.5。
示例 1:
1 | 输入:x = 4 |
示例 2:
1 | 输入:x = 8 |
约束:0 <= x <= 2^31 - 1
解题思路
- 关键洞见:若记答案为
r = ⌊√x⌋,则有r^2 <= x < (r+1)^2。因此可以在整数域上使用单调性判定。 - 方法一(二分查找,详解):利用单调性在整数区间上二分定位最大
r使得r^2 <= x。 - 方法二(牛顿迭代):迭代公式
r_{k+1} = (r_k + x / r_k) / 2收敛很快,取整数部分即可。
二分法详解(不变量、边界与正确性)
- 区间定义:使用左闭右闭区间
[left, right],含义为“答案r一定在该区间内”。 - 初始化:
left = 0,right = x(也可用更紧上界x / 2 + 1,当x >= 2时必成立)。 - 不变量:每轮迭代后,
r仍在[left, right],且区间严格收缩。 - 中点计算:
mid = left + ((right - left) >> 1),避免left + right直接相加导致溢出。 - 比较与缩小区间:
- 若
(long)mid * mid < x,说明mid偏小,目标在右侧,更新left = mid + 1。 - 若
(long)mid * mid > x,说明mid偏大,目标在左侧,更新right = mid - 1。 - 若相等,直接返回
mid。
- 若
- 终止与返回:循环以
left > right终止,此时必有right^2 <= x < left^2,因此返回right即为⌊√x⌋。 - 溢出处理:比较平方前将
mid提升为long再相乘,避免int乘法溢出导致的错误判定。 - 特殊值:
x ∈ {0,1}时流程自然成立;如需微优化可在开头直接返回x。
二分的本质:在单调布尔函数上找“边界”
把问题抽象为布尔谓词 P(m): m^2 <= x。显然 P(m) 关于 m 是单调的:当 m 增大到超过 ⌊√x⌋ 后,P(m) 从真变假。
- 目标就是找到“真到假”的分界点:最大的
m使P(m)为真。 - 这正是二分查找在单调布尔数组上的“上界”问题(max-true)。
因此,模板可表述为:在 [0, x] 上找最大 m 使 P(m) 为真;当循环结束,right 指向 max-true,left 指向 min-false。
形式化不变量与正确性(为什么返回 right)
- 循环保持:
I: right始终满足P(right),left始终不满足P(left-1)(或说left是第一个可能满足的位置)。 - 当
P(mid)为真时,mid及其左侧都为真,最大真值不在mid左边,故令left = mid + 1不会丢失 max-true。 - 当
P(mid)为假时,mid及其右侧都为假,最大真值在mid左边,故令right = mid - 1保留所有可能答案。 - 终止条件
left = right + 1,结合不变量推出:right为 max-true,left为 min-false,结论即right^2 <= x < left^2。
等价谓词与比较技巧(避免乘法溢出)
- 本文实现用
(long)mid * mid安全比较。 - 另一种常见写法是用除法比较:
mid <= x / mid等价于mid * mid <= x,可避免乘法溢出(但需要注意x / mid的整除性质以及mid != 0)。
常见陷阱与反例
- 误返回
left:循环结束后left指向第一个使P(left)为假的位置,返回它会比正确答案大 1。 - 右边界更新错误:在左闭右闭区间中,
P(mid)为假时必须right = mid - 1,若写成right = mid会导致死循环或区间不收缩。 - 中点取值与循环条件不匹配:左闭右闭应配合
while (left <= right);若误用left < right可能漏解或死循环。
执行示例(x = 8)
1 | 初始:left = 0, right = 8 |
代码实现
解法一:二分查找(你的实现)
1 | class Solution { |
解法二:牛顿迭代(常数更优)
1 | class SolutionNewton { |
方法比较
| 方面 | 二分查找 | 牛顿迭代 |
|---|---|---|
| 时间复杂度 | O(log x) |
迭代次数很少,实际常数更优 |
| 空间复杂度 | O(1) |
O(1) |
| 实现难度 | 简单、边界清晰 | 略高,需注意收敛与溢出 |
| 稳定性 | 边界与正确性易于证明 | 对初始值不敏感,但要用 long |
复杂度分析
- 二分查找:时间
O(log x),空间O(1)。 - 牛顿迭代:每次迭代为常数开销,通常 5~7 次内收敛到整数根,空间
O(1)。
关键收获
- 在整数域下通过单调性与不等式界定目标区间,是处理“向下取整”的常见套路。
- 计算平方时建议使用更宽类型(如
long)以避免溢出带来的比较错误。 - 牛顿法在需要高效近似开方时非常实用,注意以整数形式收敛并保证向下取整性质。