快乐数的数学本质是数字平方和序列是否收敛到1或陷入固定环;所有非快乐数必进入4→16→37→58→89→145→42→20→4环,故可用floyd判圈算法以o(1)空间检测。

什么是快乐数的数学本质
快乐数的判定核心不是暴力模拟,而是识别「数字平方和序列是否进入循环」。所有正整数反复计算各位平方和,最终要么收敛到 1(快乐数),要么陷入以 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 为典型代表的固定环(非快乐数)。这个环是确定的,且所有非快乐数都会在有限步内掉进它。
用快慢指针避免哈希表开销
传统方法用 std::unordered_set 记录已出现的和,空间复杂度 O(log n)。但既然存在唯一环,就可以用「Floyd 判圈算法」——只用两个整数变量,空间 O(1),且无需预存历史值。
关键点:
-
slow每次走一步:调用一次getNext(n) -
fast每次走两步:调用两次getNext(n) - 当
slow == fast时,说明已入环;此时只需判断该值是否等于1
示例函数:
int getNext(int n) {
int sum = 0;
while (n) {
int d = n % 10;
sum += d * d;
n /= 10;
}
return sum;
}
为什么不能只判 fast == 1 就返回 true
快慢指针的终止条件必须是 slow == fast,而不是中途看到 1 就退出。因为 fast 可能跳过 1(比如序列是 19 → 82 → 68 → 100 → 1,fast 从 82 直接到 100,再下一轮才到 1,但此时 slow 还没到 1)。
正确逻辑:
- 持续更新
slow和fast,直到二者相等 - 相等时,若值为
1,则是快乐数;否则一定落在环里(如4),不是快乐数 - 不需要额外处理
n == 1的边界,getNext(1)返回1,自然满足slow == fast == 1
整数溢出与输入范围的实际影响
对 int 范围内的正整数(≤ 2³¹−1),其各位平方和最大为 9²×10 = 810(10 位全是 9),远小于 INT_MAX,所以 getNext 内部不会溢出。
但要注意:
- 不要用
long long过度防御——无必要,且可能掩盖逻辑问题 - 如果输入是
unsigned int或需支持超大数,才需考虑字符串解析或模运算拆分 - 标准题设中,
1 ≤ n ≤ 2³¹ − 1,直接用int安全
真正容易被忽略的是:环检测必须跑到底,不能提前中断;而那个长度为 8 的环(含 4)就是全部非快乐数的归宿——认出它,就等于认出了所有失败路径。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











