👋 欢迎阅读

🎯 欢迎来到「x 的平方根」题解之旅! 本文将带你从"估算一个数的整数平方根"这一直观场景出发,深入理解二分查找 + 边界修正 的巧妙运用,并掌握如何折半比较平方与目标 来返回向下取整的平方根。
在开始之前,建议你先:
-
了解题目背景 :这是 LeetCode 69 题,给定非负整数
x,返回其平方根的整数部分 (向下取整),不允许 直接调用库函数。本质上,答案是最大的 k 满足 k² ≤ x ,问题转化为在 0, x 上二分定位整数根。 -
明确学习目标 :掌握二分查找边界模板 ,理解溢出防护 (
long long或除法比较),并熟练处理完全平方数与 x = 0/1 等边界情况。 -
准备好环境 :建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如
x = 8输出2,x = 4输出2)。
本文将从问题转化、二分收敛、边界修正、返回结果 到代码实现,层层递进。即使你对二分边界 还不熟悉,我们也会从"不断折半逼近,最后微调一下"这一直觉出发,让你轻松抓住核心思想 ------二分逼近根,收敛后微调。现在,让我们一起折半搜索,算出 x 的整数平方根吧! 🎯🔢
一.题目

二.做题思路
一、问题分析(前置分析)
- 题目要求:返回
x的平方根的整数部分 (floor(√x)),禁止使用库函数。 - 关键约束:
x为非负整数 (最大2³¹ - 1);结果向下取整 ;x = 0、1时结果即自身。 - 核心思路:平方函数在
[0, x]上单调递增 ,满足二分前提;答案 = 最大的 k 使 k² ≤ x。
二、算法策略(二分查找 + 收敛后修正)
核心步骤:
- 初始化区间 :
left = 0、right = x。- 二分收敛 :
while (left < right),mid下取整 (left + (right - left) / 2);计算sum = mid * mid(long long 防溢出 );若sum < x则left = mid + 1,否则right = mid。- 收敛点语义 :循环结束后
left是第一个满足 k² ≥ x 的数 (即ceil(√x))。- 三分支修正:
(left+1)² ≤ x→ 返回left + 1(防御性分支,正常不会触发);left² > x→ 返回left - 1(非完全平方:上取整减一得下取整);- 否则返回
left(完全平方:left² == x)。
示例执行过程:
| x | 阶段 | left | right | mid | mid² 比较 | 收敛点 | 修正 | 结果 |
|---|---|---|---|---|---|---|---|---|
| 8 | ① | 0 | 8 | 4 | 16 > 8 | right=4 | ||
| 8 | ② | 0 | 4 | 2 | 4 < 8 | left=3 | ||
| 8 | ③ | 3 | 4 | 3 | 9 > 8 | right=3 | 3²=9 > 8 → 返回 2 | 2 |
| 4 | ① | 0 | 4 | 2 | 4 == 4 | right=2 | ||
| 4 | ② | 0 | 2 | 1 | 1 < 4 | left=2 | 2²=4 不>4 → 返回 2 | 2 |
x=8 返回 2、x=4 返回 2,均与题目一致。
三、正确性说明(简单版本)
- 单调性保证二分有效 :
k²随k单调递增,sum < x时答案必在[mid+1, right],sum ≥ x时在[left, mid],区间单调收敛 ,不会漏解。- 收敛点确定 :循环不变量保证退出时
left == right且为第一个平方 ≥ x 的数,即ceil(√x),语义明确。- 修正分支正确 :
ceil(√x)与floor(√x)的关系------完全平方时相等(分支3);非完全平方时相差 1(分支2 减一);分支1 为永不触发的防御代码,不影响正确性。- 终止性 :
left = mid + 1与right = mid均严格缩小区间(下取整保证mid < right),不会死循环。
四、实现细节(边界防护)
- 初始化:
left = 0、right = x(long ,避免right = x与 int 混算)。- 溢出防护:
mid * mid必须存到 long long (mid最大约 46341 时平方已超 int 范围);更稳妥可改用mid <= x / mid比较。- 边界:
x = 0时right = 0,循环不进入,left = 0,返回 0;x = 1时收敛到 1,返回 1。- 复杂度:时间 O(log x) (二分折半),空间 O(1)(仅常数个变量)。
- 关键判断 :
if (sum < x) left = mid + 1; else right = mid;(左边界收敛)、(left+1)*(left+1) <= x/left*left > x(修正分支)。
五、返回值(目标映射)
- 返回
left或修正后的值:floor(√x),对应题目"平方根的整数部分"。
三.代码
cpp
class Solution
{
public:
int mySqrt(int x)
{
long left = 0; // 搜索区间左端点(long 防止边界混算溢出)
long right = x; // 搜索区间右端点:平方根必在 [0, x] 内
// 1. 二分收敛:找"第一个满足 k*k >= x"的数(即 ceil(sqrt(x)))
while (left < right)
{
long mid = left + (right - left) / 2; // mid 下取整,配合 right = mid
long long sum = (long long)mid * mid; // 必须 64 位:mid 最大约 46341,平方超 int
if (sum < x)
{
left = mid + 1; // 平方太小,答案在右半
}
else
{
right = mid; // 平方 >= x,保留 mid 向左收敛
}
}
// 此时 left == right,为 ceil(sqrt(x))
// 2. 收敛点修正为 floor(sqrt(x))
if ((left + 1) * (left + 1) <= x)
{
return (int)(left + 1); // 防御性分支:正常不会触发
}
else if (left * left > x)
{
return (int)(left - 1); // 非完全平方:上取整减一得下取整
}
return (int)left; // 完全平方:left*left == x
}
};
四、易错点分析
难点1:本代码选的是"左边界模板",收敛点是 ceil 而非 floor
cpp
if (sum < x)
left = mid + 1;
else
right = mid;
这段收敛到的是第一个平方 ≥ x 的数 (ceil(√x)),而题目要的是最大的平方 ≤ x 的数 (floor(√x))。因此代码必须事后修正 ,这是本解法"绕"的根源。若直接改用右边界模板(mid <= x / mid 时 left = mid,否则 right = mid - 1),收敛点直接就是答案,无需任何修正------两种思路都能 AC,但后一种更简洁。
难点2:事后修正的三个分支,第一个其实是"死代码"
cpp
if ((left + 1) * (left + 1) <= x)
return left + 1;
else if (left * left > x)
return left - 1;
return left;
left 是 ceil(√x),则 left + 1 的平方必然大于 x ,所以分支1 永远不会触发 ,属于冗余防御代码。真正生效的是:分支2(非完全平方,ceil 减一得 floor)和分支3(完全平方,ceil == floor)。理解这一点才能看懂这段"奇怪"的修正逻辑。
难点3:循环条件 left < right 与 mid 下取整的匹配
cpp
while (left < right)
{
long mid = left + (right - left) / 2;
...
right = mid;
}
right = mid 向左收缩要求 mid 下取整 (否则相邻区间时 mid 取 right,right = mid 不缩小,死循环 )。这里下取整配合正确;但若照抄 34 题右边界模板的上取整 写进本代码,会立刻死循环------模板必须成套使用。
五、流程图

🎯 闭幕

🎉 恭喜你完成了「x 的平方根」问题的学习!
为了巩固知识并进一步拓展,建议你:
🚀 动手实践
在 LeetCode 上提交代码,尝试不同的测试用例。
💡 深入思考
-
本题使用二分查找 找上取整平方根 (即第一个
mid*mid >= x),最后再修正为下取整。为什么不直接找下取整平方根(mid*mid <= x的最大 mid)? 直接找下取整在边界处理上会遇到什么问题? -
二分收敛条件为
left < right,mid下取整,当sum >= x时right = mid。这种写法是否会出现死循环? 请结合区间长度变化分析。 -
代码最后的修正逻辑包含三个分支:
(left+1)^2 <= x、left^2 > x、left^2 == x。为什么需要这些额外的修正? 如果二分直接找下取整,是否可以省略这些分支? -
如果
x = 0或x = 1,代码的二分循环和修正逻辑是否能正确处理?请分别验证。
如果你觉得本文对你有所帮助,欢迎:
👍 点赞 / 收藏
👤 关注作者 ,获取更多题解
💬 留言交流你的疑问或优化思路
📌 深入思考答案
-
不直接找下取整 是因为二分的收敛方向若为
left = mid(保留 mid),需配合 上取整 防止死循环,而找下取整时mid*mid <= x分支执行left = mid,若mid下取整会导致相邻区间死循环(如 left=0, right=1 时 mid=0,left 无法前进)。找上取整再修正可以统一使用下取整的mid计算方式,更简洁。 -
不会死循环 :因为
mid = left + (right-left)/2下取整,当sum >= x时right = mid,区间长度每次至少减半(当 length > 1 时,mid < right,right向左移动),最终收敛到一点。 -
修正分支的必要性 :因为二分找的是 上取整 (第一个
mid^2 >= x),而题目要的是下取整(floor(sqrt(x))),因此需要根据收敛点与 x 的关系进行调整:若left^2 == x则正好;若left^2 > x则减 1 得下取整;(left+1)^2 <= x分支是防御性代码,正常不会触发。若直接找下取整,可改用right = mid - 1配合left = mid(需上取整)实现,则无需修正。 -
x=0时:left=0, right=0,不进入循环,left^2 == x触发,返回 0,正确。x=1时:left=0, right=1,mid=0,sum=0 < 1,left=1,循环结束,left^2 == x触发,返回 1,正确。
祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨