LeetCode LCR 072. x 的平方根
题目描述
题意分析
给一个非负整数
x,返回它算术平方根的整数部分,小数部分一律舍去。也就是求最大的非负整数k,使得 $k^2 \le x$。「向下取整」这个要求把问题从「求实数根」变成了「在整数上找边界」:候选答案是 $0, 1, 2, \dots$ 这个有序序列,而「$k^2 \le x$」这个性质随着
k增大只会从真变假一次——小的k全部满足,大的k全部不满足。要找的正是最后一个满足条件的k。这是典型的「二分答案」形态:二分的对象不是给定数组的下标,而是答案本身的取值域。题目通常还会禁止使用内置的开方函数,这一条正是把考点锁在「如何自己逼近」上。
约束是
x不超过 $2^{31} - 1$。这条约束在实现上有决定性影响:mid * mid在mid稍大时就会超出 32 位有符号整数,直接比较必然溢出。边界上要注意:
x = 0时答案是 0,x = 1时答案是 1,这两个最小规模必须能正确返回;x为完全平方数时答案恰好是根本身,不能被舍成小一位。
解法:二分查找判定答案
核心思路
暴力做法是从 0 开始逐个试,累加到第一个平方超过
x的数就停。$O(\sqrt{x})$,在 $x$ 接近 $2^{31}$ 时约 4.6 万次循环,能过但没体现出对单调性的利用。瓶颈在于线性试探每次只排除一个候选,而「$k^2 \le x$」这个性质是单调的:一旦发现某个
k的平方已经超过x,比它大的所有k都不必再试;反之若某个k满足,比它小的都不必再试。每次比较本可以砍掉一半候选。于是把答案的取值域
[0, x]当作搜索区间做二分。取x作为上界是安全的:对任意非负整数x,$\sqrt{x} \le x$ 恒成立(x = 0和x = 1时取等)。判定条件是
mid * mid <= x,但因为溢出风险,实际写成等价的mid <= x / mid(整数除法)。这个变形是安全的:当mid <= x / mid成立时,由整数除法的性质有 $mid \cdot mid \le x$;当它不成立时,$mid > x / mid$ 意味着 $mid \cdot mid > x$。两边同时把一次乘法换成一次除法,值域就再也不会溢出。维持的不变量是:答案始终落在
[left, right]内;left及其左侧的所有候选都满足 $k^2 \le x$,right右侧的所有候选都不满足。这是「找最后一个为真」的右边界二分:判定为真时保留mid(left = mid),为假时排除(right = mid - 1)。由于
left = mid不会让区间缩短,中点必须向上取整(mid = (left + right + 1) / 2),否则在区间只剩两个元素时mid恒等于left,赋值后区间原地踏步,陷入死循环。取整方向与收缩方式的这种配对关系,是右边界二分的固定搭配。
解题步骤
- 令
left = 0、right = x,把整个可能的答案域纳入搜索范围。上界取x而不是x / 2之类的估计值,是为了让x = 1这种小规模也被正确覆盖。- 循环条件写
left < right,收缩到唯一候选时退出。x = 0时循环一次都不进,直接返回 0,边界天然成立。- 中点用
(left + right + 1) >>> 1向上取整。这个+1是与下面left = mid配套的:右边界二分若用向下取整,两元素区间会死循环。无符号右移同时防止了left + right + 1在x接近 $2^{31}$ 时溢出成负数。- 判定用
mid <= x / mid而不是mid * mid <= x。后者在mid超过 46341 时就会溢出 32 位整数,得到负值并被误判为满足条件。除法形式与它数学等价却永不溢出。- 判定为真时
left = mid:mid自身满足 $mid^2 \le x$,它有资格当答案,必须保留;把它排除会丢掉完全平方数的正确解。- 判定为假时
right = mid - 1:mid的平方已经超了,它和它右边的候选全部出局。- 退出循环时
left == right,返回left。因为不变量保证了它是最后一个满足条件的候选,即整数平方根。以
x = 8走一遍:初始left = 0、right = 8。第一轮mid = (0 + 8 + 1) / 2 = 4,判定4 <= 8 / 4 = 2为假,说明 $4^2 = 16 > 8$,right = 3。第二轮mid = (0 + 3 + 1) / 2 = 2,判定2 <= 8 / 2 = 4为真,说明 $2^2 = 4 \le 8$,保留,left = 2。第三轮mid = (2 + 3 + 1) / 2 = 3,判定3 <= 8 / 3 = 2为假,说明 $3^2 = 9 > 8$,right = 2。此时left == right == 2,返回 2——正是 $\sqrt{8} \approx 2.83$ 向下取整的结果。注意第三轮若中点向下取整会得到 2,与left相同且left = mid后区间不变,正是那个必须靠+1规避的死循环。
代码实现
class Solution {
public int mySqrt(int x) {
int left = 0, right = x;
while (left < right) {
int mid = (left + right + 1) >>> 1;
if (mid <= x / mid) {
left = mid;
} else {
right = mid - 1;
}
}
return left;
}
}
func mySqrt(x int) int {
left, right := 0, x
for left < right {
mid := left + (right-left+1)>>1
if mid <= x/mid {
left = mid
} else {
right = mid - 1
}
}
return left
}
复杂度分析
- 时间复杂度:$O(\log x)$,搜索区间长度为 $x$ 且每轮至少减半,因此最多约 31 轮循环,每轮只做一次整数除法与比较。
- 空间复杂度:$O(1)$,只用了
left、right、mid三个整数变量,没有任何辅助结构或递归栈。
关键点总结
- 「求满足某单调性质的最大整数」是二分答案的标准信号。二分的对象是答案的值域而不是某个数组的下标,识别出这一点就能把一大类「最小化最大值」「最大化最小值」的题纳入同一套模板。
- 右边界二分(找最后一个为真)必须用向上取整的中点,配
left = mid与right = mid - 1;左边界二分(找第一个为真)用向下取整,配right = mid与left = mid + 1。取整方向与收缩方式必须成对出现,混用必死循环。- 判定式里出现乘法且值域接近类型上限时,优先改写成除法或提前用更宽的类型。
a * a <= x改成a <= x / a是最省事的等价变形,不需要long也不需要浮点。- 搜索上界要选一个可证明的安全值。这里用
x而不是精细估计,是因为多几轮二分完全不影响复杂度,而估计失误会直接丢解。- 二分的正确性完全依赖「判定性质随候选单调变化」。写代码前先确认「小的全真、大的全假」,再套模板,顺序不能反。
- 面试视角:面试官通常会追问溢出如何处理,这是本题最核心的得分点。主动说明「改用
mid <= x / mid规避乘法溢出」,比写完再被指出要好得多;也可以答「用long存mid * mid」,但要说清 Java 中int相乘会先溢出再赋值,必须写成(long) mid * mid。- 面试视角:常见追问是「怎么求到小数点后若干位」或「有没有更快的方法」。前者答把二分搬到实数域、以精度
eps为终止条件;后者答牛顿迭代法 $x_{n+1} = (x_n + a / x_n) / 2$,收敛速度是平方级,能报出这个名字并写出迭代式通常就足够了。
易错点总结
- 错误写法:判定写成
mid * mid <= x。用例x = 2147395600→ 二分过程中mid会取到 46341 以上,mid * mid溢出 32 位整数变成负数,被误判为满足条件,返回值远大于正确答案 46340。- 错误写法:中点用向下取整
(left + right) >>> 1却仍写left = mid。用例x = 8→ 区间收缩到[2, 3]时mid恒为 2,left = 2后区间不变,死循环。- 错误写法:判定为真时写
left = mid + 1。用例x = 4→ 正确答案 2 在某轮成为mid并被排除,最终返回 1,完全平方数全部会少 1。- 错误写法:判定为假时写
right = mid。用例x = 8→ 区间无法在右侧收缩到位,配合向上取整的中点会在两元素区间反复取到同一个mid,死循环。- 错误写法:
right初始化为x / 2而不特判小值。用例x = 1→ 搜索区间是[0, 0],直接返回 0,正确答案是 1。- 错误写法:中点写成
(left + right + 1) / 2且用有符号除法。用例x = 2147483647→left + right + 1溢出成负数,mid变负,x / mid得到负值,判定与收缩全部错乱。- 错误写法:忘记
x = 0的情况,直接从left = 1开始二分。用例x = 0→ 区间[1, 0]非法,循环不进入却返回 1,正确答案是 0;更糟的实现会在x / mid处除以 0 抛异常。- 错误写法:用
Math.sqrt求出浮点根再强转int。用例x = 2147395600→ 浮点表示在大数上存在精度误差,可能得到 46339.999… 被截断成 46339,正确答案是 46340;何况题目通常明确禁止内置开方。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 69. x 的平方根 | 简单 | 与本题同题,可用来对照二分答案与牛顿迭代两条路线 |
| 367. 有效的完全平方数 | 简单 | 只需判定是否恰好相等,答案从边界值退化成布尔判断 |
| 875. 爱吃香蕉的珂珂 | 中等 | 判定条件从一次乘除变成一轮 $O(n)$ 的可行性检查 |
| 1011. 在 D 天内送达包裹的能力 | 中等 | 二分答案配合贪心分段,且下界必须取单件最大值而非 1 |
| 410. 分割数组的最大值 | 困难 | 最小化最大值的经典形态,判定同样是贪心分段计数 |
| 1482. 制作 m 束花所需的最少天数 | 中等 | 二分的是天数,判定需扫描数组统计连续段,还要先判无解 |
| 35. 搜索插入位置 | 简单 | 左边界二分的样板,可对照两套取整方向与收缩规则的配对关系 |