👋 欢迎阅读

🎯 欢迎来到「快乐数」题解之旅! 本文将带你从"判断一个数在迭代平方和过程中是否会陷入循环"这一数学问题出发,深入理解快慢指针(Floyd判圈算法) 的经典应用,并掌握如何高效检测循环而不必存储历史状态。
在开始之前,建议你先:
-
了解题目背景 :这是 LeetCode 202 题,给定一个正整数 nn,每次将其替换为各位数字的平方和 ,重复这一过程,若最终变为 1 则该数为快乐数,否则会陷入不含 1 的循环 。本质上,这是一个检测链式变换是否进入循环的问题,类似于判断链表是否有环。
-
明确学习目标 :掌握两种解法------哈希集合存储历史值 (直观但需额外空间)和快慢指针(Floyd判圈算法)(空间 O(1)O(1)),理解为什么平方和序列一定会进入循环(值域有限),以及快慢指针如何通过"每次慢走一步、快走两步"来检测循环。熟练实现位运算与辅助函数,并处理边界情况。
-
准备好环境 :建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如 n=19n=19 输出
true,n=2n=2 输出 `false ))。
本文将从问题转化、平方和计算、循环检测(哈希表 vs 快慢指针) 到代码实现,层层递进。即使你对环形链表检测还不熟悉,我们也会从"这个计算过程就像走一条链,要么到1,要么绕圈"这一直觉出发,让你轻松抓住核心思想 ------快慢指针能判断是否有环,而且空间更省。现在,让我们一起在数字的平方和变换中,找出那个快乐数的密码吧! 🧮✨
一.题目
🎯 欢迎来到「移动零」题解之旅! 本文将带你从"将所有零移到数组末尾,同时保持非零元素顺序"这一数组操作问题出发,深入理解双指针(快慢指针) 的经典应用,并掌握如何原地修改数组,实现高效的一次遍历。
在开始之前,建议你先:
-
了解题目背景 :这是 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 = n,fast = square(n)。 -
循环直到
slow == fast(说明检测到环)。 -
若相遇时
fast == 1,则循环是1 -> 1 -> ...,返回true;否则进入其他循环,返回false。
三、正确性说明(简单版本)
快乐数过程中的平方和序列 要么最终到达 1 ,要么会进入一个不包含 1 的循环(因为数值范围有限,必然重复)。快慢指针 能在不记录历史的情况下检测环的存在。当快慢指针相遇时,若相遇点值为 1,则说明序列中出现了 1 的循环,即原数是快乐数;否则说明进入了其他循环,不是快乐数。该算法可检测所有情况,正确性由判圈算法保证。
四、实现细节(边界防护)
-
辅助函数
square(int n)负责计算各位数字的平方和,注意 循环取模 直到n变为 0。 -
初始化
slow = n,fast = 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 = n,fast = 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 = n,fast = square(n)是为了让快指针先走一步,否则 while 循环一开始slow == fast会直接退出,无法判断循环。如果都从n开始,则需要改用do-while结构。
🔍 延伸挑战答案
-
挑战1 :算法框架 完全不变 ,只需将
square函数中的平方改为立方(t*t*t)即可,因为底层逻辑仍然是迭代函数求值 + 判圈,快乐数的定义仅改变平方和的函数形式,判圈逻辑通用。 -
挑战2 :可在快慢指针相遇后,用 哈希集合 记录从
n开始到相遇的所有数字,然后从相遇点继续走,记录直到回到起点,即可得到完整循环序列;或者修改为 哈希集合 方案,在迭代过程中记录所有出现过的数字,当发现重复且不为 1 时直接输出该循环段。
祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨