LeetCode 202:快乐数(双指针问题) —— 题解

👋 欢迎阅读

🎯 欢迎来到「快乐数」题解之旅! 本文将带你从"判断一个数在迭代平方和过程中是否会陷入循环"这一数学问题出发,深入理解快慢指针(Floyd判圈算法) 的经典应用,并掌握如何高效检测循环而不必存储历史状态。

在开始之前,建议你先:

  • 了解题目背景 :这是 LeetCode 202 题,给定一个正整数 nn,每次将其替换为各位数字的平方和 ,重复这一过程,若最终变为 1 则该数为快乐数,否则会陷入不含 1 的循环 。本质上,这是一个检测链式变换是否进入循环的问题,类似于判断链表是否有环。

  • 明确学习目标 :掌握两种解法------哈希集合存储历史值 (直观但需额外空间)和快慢指针(Floyd判圈算法)(空间 O(1)O(1)),理解为什么平方和序列一定会进入循环(值域有限),以及快慢指针如何通过"每次慢走一步、快走两步"来检测循环。熟练实现位运算与辅助函数,并处理边界情况。

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

本文将从问题转化、平方和计算、循环检测(哈希表 vs 快慢指针) 到代码实现,层层递进。即使你对环形链表检测还不熟悉,我们也会从"这个计算过程就像走一条链,要么到1,要么绕圈"这一直觉出发,让你轻松抓住核心思想 ------快慢指针能判断是否有环,而且空间更省。现在,让我们一起在数字的平方和变换中,找出那个快乐数的密码吧! 🧮✨

一.题目

202. 快乐数 - 力扣(LeetCode)

🎯 欢迎来到「移动零」题解之旅! 本文将带你从"将所有零移到数组末尾,同时保持非零元素顺序"这一数组操作问题出发,深入理解双指针(快慢指针) 的经典应用,并掌握如何原地修改数组,实现高效的一次遍历。

在开始之前,建议你先:

  • 了解题目背景 :这是 LeetCode 283 题,给定一个数组 nums,要求将所有 0 移动到数组末尾,并保持非零元素的相对顺序不变 ,且必须原地操作 (不能复制新数组)。这是数组操作中的基础题,也是双指针思想的入门经典。

  • 明确学习目标 :掌握双指针解法 ------维护一个"慢指针"指向已处理好的非零序列的末尾,用"快指针"遍历数组,遇到非零元素则交换(或覆盖)到慢指针位置,最后将剩余位置填零。理解为什么这种"只关心非零元素,遇到零就跳过"的策略能保持相对顺序,并熟练处理边界(如全零数组或全非零数组)。

  • 准备好环境 :建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如 nums = [0,1,0,3,12] 输出 [1,3,12,0,0])。

本文将从问题转化、双指针策略设计(快慢指针详解)、代码模拟 到复杂度分析,层层递进。即使你对双指针还不熟悉,我们也会从"用慢指针记录非零元素应该放的位置"这一直觉出发,让你轻松抓住核心思想 ------快指针负责探路,慢指针负责记录,所有非零元素依次往前靠,零自然被挤到后面。现在,让我们一起把零"搬运"到末尾,让数组焕然一新吧! 🔄📦

二.做题思路

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

给定正整数 n,不断将其替换为 各位数字的平方和 ,重复此过程。若最终能变成 1,则为快乐数;否则会进入 不含 1 的无限循环

核心观察 :平方和序列要么收敛到 1,要么进入循环。因此,检测循环 是解决问题的关键。


二、算法策略(快慢指针)

  • 使用 Floyd 判圈算法(快慢指针) 检测序列中的循环:

    • 慢指针 slow 每次走一步(计算一次平方和);

    • 快指针 fast 每次走两步(计算两次平方和)。

  • 初始化:slow = nfast = square(n)

  • 循环直到 slow == fast(说明检测到环)。

  • 若相遇时 fast == 1,则循环是 1 -> 1 -> ...,返回 true;否则进入其他循环,返回 false


三、正确性说明(简单版本)

快乐数过程中的平方和序列 要么最终到达 1 ,要么会进入一个不包含 1 的循环(因为数值范围有限,必然重复)。快慢指针 能在不记录历史的情况下检测环的存在。当快慢指针相遇时,若相遇点值为 1,则说明序列中出现了 1 的循环,即原数是快乐数;否则说明进入了其他循环,不是快乐数。该算法可检测所有情况,正确性由判圈算法保证。


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

  • 辅助函数 square(int n) 负责计算各位数字的平方和,注意 循环取模 直到 n 变为 0。

  • 初始化 slow = nfast = square(n)(快指针先走一步,防止初始相等误判)。

  • 循环条件为 slow != fast,每次循环:

    • slow = square(slow)

    • fast = square(fast); fast = square(fast);(快指针走两步)。

  • 退出循环后判断 fast == 1(或 slow == 1 均可)。

  • 时间复杂度:O(循环步数),空间复杂度 O(1)。


五、返回值(目标映射)

返回 true 表示 n 是快乐数,否则返回 false

三.代码

cpp 复制代码
class Solution 
{
public:
    // 辅助函数:计算一个数各位数字的平方和
    int square(int n) 
    {
        int sum = 0;   // 累计平方和
        int t = 0;     // 临时变量,存储当前位的数字

        // 循环取出 n 的每一位数字
        while (n > 0) 
        {
            t = n % 10;          // 取出最低位
            sum += t * t;        // 累加该位的平方
            n = n / 10;          // 去掉最低位
        }
        return sum;
    }

    bool isHappy(int n) 
    {
        // 算法思路:快乐数的计算过程本质上是对数值进行迭代(f(n) = 各位平方和),
        // 这个过程要么最终到达1(快乐数),要么进入一个不包含1的循环。
        // 使用快慢指针(Floyd判圈算法)检测循环:
        //   - slow 每次走一步(调用一次 square)
        //   - fast 每次走两步(调用两次 square)
        // 如果它们相遇,说明存在循环;如果相遇时值为1,则说明是快乐数。

        int slow = n;              // 慢指针初始指向 n
        int fast = square(n);      // 快指针初始指向 f(n)

        // 循环直到快慢指针相遇(即检测到循环)
        while (slow != fast) 
        {
            slow = square(slow);          // 慢指针走一步

            // 快指针走两步(连续调用两次 square)
            fast = square(fast);
            fast = square(fast);
        }

        // 相遇时,如果 fast(或 slow)等于1,说明循环为1 -> 1 -> ...,是快乐数;
        // 否则进入了其他循环(如4 -> 16 -> 37 -> ...),不是快乐数。
        if (fast == 1) 
        {
            return true;
        } 
        else 
        {
            return false;
        }
    }
};

四、流程图

🎯 闭幕

🎉 恭喜你完成了「快乐数」问题的学习!

为了巩固知识并进一步拓展,建议你:

🚀 动手实践

在 LeetCode 上提交代码,尝试不同的测试用例。

💡 深入思考

  • 本题使用 快慢指针(Floyd判圈算法) 检测循环,slow 每次走一步(调用一次 square),fast 每次走两步(调用两次 square)。请问 为什么快慢指针一定能相遇?如果数字序列不存在循环会怎样?

  • 辅助函数 square 计算各位数字的 平方和 ,这个过程是否一定会在有限步内进入 循环?你能从数学上解释原因吗?(提示:数字位数与平方和的关系)

  • 初始时 slow = nfast = square(n),为什么不让两者都从 n 开始?如果都从 n 开始,while 循环还能正确工作吗?

📚 延伸挑战

  • 如果将规则改为 各位数字的立方和 (而不是平方和),判断一个数是否为"快乐数"的算法框架是否需要改变?只修改 square 函数是否足够?

  • 如果要求输出 循环中的全部数字(例如当不是快乐数时,输出进入循环的所有数),如何基于当前代码做最小改动来实现?

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

👍 点赞 / 收藏

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

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


📌 深入思考答案

  • 快慢指针一定能相遇,因为平方和迭代在有限状态(整数范围 1~2^31-1)上必然出现重复,一旦重复就形成环,快指针终会追上慢指针,这是 Floyd 判圈算法的核心。若不存在循环,只有到达 1 后永远停留在 1(即 1→1 的循环),快慢指针依然会相遇于 1。

  • 平方和迭代必定进入循环 ,因为对于任意 n,其各位平方和最大不超过 9^2 * 位数,当位数较多时(例如 10 位数),平方和最大为 810,而后续值被限制在 1~810 的有限范围内,因此最多迭代 810 次后必有重复,进入循环。

  • 初始设置 slow = nfast = square(n) 是为了让快指针先走一步,否则 while 循环一开始 slow == fast 会直接退出,无法判断循环。如果都从 n 开始,则需要改用 do-while 结构。

🔍 延伸挑战答案

  • 挑战1 :算法框架 完全不变 ,只需将 square 函数中的平方改为立方(t*t*t)即可,因为底层逻辑仍然是迭代函数求值 + 判圈,快乐数的定义仅改变平方和的函数形式,判圈逻辑通用。

  • 挑战2 :可在快慢指针相遇后,用 哈希集合 记录从 n 开始到相遇的所有数字,然后从相遇点继续走,记录直到回到起点,即可得到完整循环序列;或者修改为 哈希集合 方案,在迭代过程中记录所有出现过的数字,当发现重复且不为 1 时直接输出该循环段。

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

相关推荐
tudousisi2223 小时前
P1216 [IOI 1994 / USACO1.5] 数字三角形 题解复盘
c++
cpp_25014 小时前
P1113 [USACO02FEB] 杂务
数据结构·c++·算法·动态规划·图论·拓扑排序·洛谷题解
月光船幽幽4 小时前
门控函数SHS阈值与调制机制解析
人工智能·python·算法
此生决int4 小时前
深入理解C++系列(11)——stack、queue和priority_queue
开发语言·c++
Doraemomo4 小时前
数据结构-哈希表
数据结构·算法·散列表
君君思密达4 小时前
Leetcode Hot 100 题目详解-哈希表
数据结构·算法·leetcode
lucas_AI4 小时前
Muse Glimmer 30B:Meta 难得给的真开源,强在哪、虚在哪
人工智能·算法
ai产品老杨4 小时前
国产NPU视觉算法完整流程
算法
过期的秋刀鱼!4 小时前
带替换的采样
人工智能·python·算法·决策树·机器学习