LeetCode 69:x 的平方根(二分查找) —— 题解

👋 欢迎阅读

🎯 欢迎来到「x 的平方根」题解之旅! 本文将带你从"估算一个数的整数平方根"这一直观场景出发,深入理解二分查找 + 边界修正 的巧妙运用,并掌握如何折半比较平方与目标返回向下取整的平方根

在开始之前,建议你先:

  • 了解题目背景 :这是 LeetCode 69 题,给定非负整数 x,返回其平方根的整数部分 (向下取整),不允许 直接调用库函数。本质上,答案是最大的 k 满足 k² ≤ x ,问题转化为0, x 上二分定位整数根

  • 明确学习目标 :掌握二分查找边界模板 ,理解溢出防护long long 或除法比较),并熟练处理完全平方数与 x = 0/1 等边界情况。

  • 准备好环境 :建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如 x = 8 输出 2x = 4 输出 2)。

本文将从问题转化、二分收敛、边界修正、返回结果 到代码实现,层层递进。即使你对二分边界 还不熟悉,我们也会从"不断折半逼近,最后微调一下"这一直觉出发,让你轻松抓住核心思想 ------二分逼近根,收敛后微调。现在,让我们一起折半搜索,算出 x 的整数平方根吧! 🎯🔢

一.题目

69. x 的平方根 - 力扣(LeetCode)

二.做题思路

一、问题分析(前置分析)

  • 题目要求:返回 x 的平方根的整数部分floor(√x)),禁止使用库函数。
  • 关键约束:x非负整数 (最大 2³¹ - 1);结果向下取整x = 01 时结果即自身。
  • 核心思路:平方函数在 [0, x]单调递增 ,满足二分前提;答案 = 最大的 k 使 k² ≤ x

二、算法策略(二分查找 + 收敛后修正)

核心步骤:

  1. 初始化区间left = 0right = x
  2. 二分收敛while (left < right)mid 下取整left + (right - left) / 2);计算 sum = mid * midlong long 防溢出 );若 sum < xleft = mid + 1,否则 right = mid
  3. 收敛点语义 :循环结束后 left第一个满足 k² ≥ x 的数 (即 ceil(√x))。
  4. 三分支修正
    • (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 单调递增,sum < x 时答案必在 [mid+1, right]sum ≥ x 时在 [left, mid]区间单调收敛不会漏解
  • 收敛点确定 :循环不变量保证退出时 left == right 且为第一个平方 ≥ x 的数,即 ceil(√x),语义明确。
  • 修正分支正确ceil(√x)floor(√x) 的关系------完全平方时相等(分支3);非完全平方时相差 1(分支2 减一);分支1 为永不触发的防御代码,不影响正确性。
  • 终止性left = mid + 1right = mid 均严格缩小区间(下取整保证 mid < right),不会死循环

四、实现细节(边界防护)

  • 初始化:left = 0right = xlong ,避免 right = x 与 int 混算)。
  • 溢出防护:mid * mid 必须存到 long longmid 最大约 46341 时平方已超 int 范围);更稳妥可改用 mid <= x / mid 比较。
  • 边界:x = 0right = 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 / midleft = 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;

leftceil(√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 < rightmid 下取整,当 sum >= xright = mid这种写法是否会出现死循环? 请结合区间长度变化分析。

  • 代码最后的修正逻辑包含三个分支:(left+1)^2 <= xleft^2 > xleft^2 == x为什么需要这些额外的修正? 如果二分直接找下取整,是否可以省略这些分支?

  • 如果 x = 0x = 1,代码的二分循环和修正逻辑是否能正确处理?请分别验证。

如果你觉得本文对你有所帮助,欢迎:

👍 点赞 / 收藏

👤 关注作者 ,获取更多题解

💬 留言交流你的疑问或优化思路


📌 深入思考答案

  • 不直接找下取整 是因为二分的收敛方向若为 left = mid(保留 mid),需配合 上取整 防止死循环,而找下取整时 mid*mid <= x 分支执行 left = mid,若 mid 下取整会导致相邻区间死循环(如 left=0, right=1 时 mid=0,left 无法前进)。找上取整再修正可以统一使用下取整的 mid 计算方式,更简洁。

  • 不会死循环 :因为 mid = left + (right-left)/2 下取整,当 sum >= xright = 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,正确。

祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨

相关推荐
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】项目的数据结构设计
数据结构·人工智能·大模型·sdk·文件系统·岗位
j7~1 小时前
【C++】《C++二叉搜索树(BST)从入门到精通:概念、实现与Key/Value模型全解析》
开发语言·c++·学习·二叉搜索树
hehelm1 小时前
仿muduo库实现高并发服务器—Channel类
linux·服务器·开发语言·网络·c++
sel_91 小时前
【Pytorch】PyTorch 深度学习框架详解:从安装到实战
人工智能·pytorch·深度学习·算法·语言模型
学习星球1 小时前
【LeetCode算法题精讲】图算法精讲——从图遍历到拓扑排序
数据结构·算法·leetcode·图搜索
余额瞒着我当琳1 小时前
C++ list第二讲数据结构修炼:迭代器源码 + 栈队列适配器 + LeetCode 三道高频题
数据结构·c++·list
hehelm1 小时前
仿muduo库实现高并发服务器—EventLoop
linux·服务器·网络·c++
_Narcissus_1 小时前
常见数论算法笔记
数据结构·c++·算法·高精度·数论·快速幂·质数筛
余额瞒着我当琳1 小时前
C++STL--list底层实现,迭代器分类,模拟list的迭代器封装、实现
java·开发语言·c++