快乐数的数学定义是:一个正整数反复计算各位数字的平方和,若最终结果为1,则为快乐数;否则必落入4→16→37→58→89→145→42→20→4的固定循环,即非快乐数。

什么是快乐数的数学定义
一个正整数反复将各位数字平方和相加,最终结果为 1,就是快乐数;如果进入循环且永远不出现 1(比如掉进 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 这个环),就不是快乐数。
关键点在于:**所有非快乐数最终都会落入以 4 为起点的固定循环**。这是数学上已证明的性质,不是靠猜——所以检测是否进入这个环,比用哈希表存所有见过的数更轻量。
用快慢指针判断循环(推荐)
把“计算下一位数字平方和”看作一个链表的 next 操作,问题就变成「判断链表是否有环」。快慢指针无需额外空间,O(1) 空间复杂度,且避免了 std::unordered_set 的哈希开销和内存分配。
实现要点:
立即学习“C++免费学习笔记(深入)”;
-
get_next函数要正确拆解数字:对n取模10得个位,再除以10整除,循环直到n == 0 - 快指针每次走两步:
slow = get_next(slow); fast = get_next(get_next(fast)); - 初始时
slow = n,fast = get_next(n),避免一开始就相等 - 循环条件是
slow != fast && fast != 1;退出后只需检查slow == 1或fast == 1
示例片段:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
int get_next(int n) {
int sum = 0;
while (n) {
int d = n % 10;
sum += d * d;
n /= 10;
}
return sum;
}
<p>bool isHappy(int n) {
int slow = n, fast = get_next(n);
while (slow != fast && fast != 1) {
slow = get_next(slow);
fast = get_next(get_next(fast));
}
return fast == 1;
}为什么不用 unordered_set 存历史值
虽然直观,但有实际代价:
- 每次插入和查找平均 O(1),但常数大,尤其小数字(如
100以内)很快收敛,哈希表反而拖慢 - 最坏情况可能存几十个中间数(比如从
999999999开始),而快慢指针最多跑几十步就出结果 - 在嵌入式或内存受限场景,避免动态分配更稳妥
只有当你同时需要记录路径、或调试中间过程时,std::unordered_set 才值得引入。
注意整型溢出和边界处理
int 在 C++ 中通常为 32 位,最大值约 2e9。但快乐数变换中,最大的单步平方和来自 999999999(9 个 9):9×81 = 729,远小于 INT_MAX,所以不会溢出。
但仍需注意:
-
get_next中用int d = n % 10是安全的,但若误写成n % -10会出负余数(取决于编译器),应确保n > 0 - 输入保证是正整数,无需检查 assert(n > 0)
- 测试用例
1和10必须返回 true,2和4必须返回 false —— 这些是验证快慢指针逻辑是否绕过初始相等的关键点
真正容易被忽略的是:快慢指针初始化不能都设为 n,否则循环体一次都不进就返回错误结果。

















