如果你刷题刷到一定量,大概率会有一种感觉:有些题长得像小学数学,真要现场把原理讲透,反而会卡壳。LeetCode 202 快乐数就是这么一道题。它躺在热门100题列表里,标着 easy,解法也很短,但它背后牵扯出来的判环思想,几乎可以无缝移植到链表成环、循环依赖检测、递归死循环排查这些更“高级”的题目里。
题目本身一句话就能说完:给一个正整数 n,每次把它替换成各个位置上数字的平方和。不停地重复这个过程,如果某一次得到 1,它就是快乐数;如果陷入无限循环且永远到不了 1,它就不是快乐数。比如 n = 19 就是标准快乐数,从 19 出发会走出一条到 1 的路径;而 n = 2 则会进到一条循环轨道里,怎么转都回不到 1。
我第一次做这道题时,心里想的是:这还不简单?直接 while 循环套一层平方和计算,等 n 变成 1 就返回 true,不等就是 false。结果一运行就发现不对劲——n = 2 的时候程序直接无限循环了。这恰好点出了这道题真正的考点:你怎么知道它会循环?你怎么判断它永远变不到 1?下面我把这道题的完整拆解过程写出来,包括两种主流解法的思路、证明和代码,以及我在实际刷题中绕过的坑。
1. 透过题面看本质:这不是求值问题,而是判环问题
1.1 用两个例子直观感受“快乐”和“不快乐”
先动手算一个快乐数:n = 19。
第一次平方和:1² + 9² = 82
第二次:8² + 2² = 68
第三次:6² + 8² = 100
第四次:1² + 0² + 0² = 1
到了 1 之后,如果继续做同样的运算,1² = 1,会一直停留在 1,这就是一个稳定的“终点”。
再看一个不快乐的例子:n = 2。
2 → 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4
可以看到 4 在第十步又出现了。因为每一步的运算规则是确定的,一旦某个数字第二次出现,后面所有数字都会完全复刻上一轮路径,于是进入一个死循环。2 永远也不可能变成 1。
这两个例子合在一起,基本就是整个题目的全貌了:快乐数最终会滑入 1 的自循环;非快乐数最终会滑入某个不包含 1 的循环。题目表面是在问“能不能变成 1”,实际是在问“这个数字变换过程最后会走进哪条循环轨道”。
1.2 无限循环是题面给的最重要提示
很多新手会忽略题面里“也可能是无限循环但始终变不到 1”这句话。它不是在讲故事,而是在告诉你解题方向:必须能识别循环。
如果只会无脑迭代,遇到非快乐数就会卡死在 while 里。测试用例只给几个数的时候还发现不了,一旦把 2、3、4 这类非快乐数塞进去,程序就当场超时。我第一次就是这样,超时后才重新读题,意识到问题核心是“如何判断重复状态”。
这类题的通用解法套路,其实就是一句话:判断一个过程中是否出现了重复状态。重复状态一旦出现,就不可能有未来;反过来说,如果直到变成 1 都还没出现重复,那它就是快乐数。
1.3 状态机视角的建模
把每次“替换为各位平方和”看作一次状态转移,这个题目可以重新描述成一张图:
- 每个正整数是一个节点;
- 从数字 a 连一条有向边到 f(a),其中 f(a) 等于 a 各位数字平方和;
- 不管从哪个节点出发,沿着有向边走,最终必然走进一个环;
- 如果这个环是 1 → 1 → 1,那么起点是快乐数;如果是其他环,起点不是快乐数。
这就不是一道简单的模拟题了,而是图论里的“环检测”问题。检测有向图里有没有环,最常用的两个手段就是哈希集合和快慢指针。这两个手段正好对应快乐数的两种主流解法,也对应面试官追问时的两个层次。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 哈希集合实现:把每一步当成状态去判重
2.1 为什么一个重复状态就能决定整个序列的命运
在确定性系统里,下一个状态完全由当前状态决定。什么意思?当前数字是 89,下一位一定是 8² + 9² = 145;当前数字是 145,下一位一定是 1² + 4² + 5² = 42。中
