快乐数是指正整数经反复计算各位数字平方和最终得1的数;判定逻辑用哈希集合记录中间值,遇1返回true,遇重复返回false,避免仅判4等不鲁棒优化。

什么是快乐数,以及判定逻辑怎么写
快乐数的定义是:对一个正整数反复计算其各位数字的平方和,最终结果为 1,则它是快乐数;如果进入循环且永远到不了 1,就不是快乐数。关键在于——你不能靠无限循环去等结果,必须识别「陷入循环」这个终止条件。
最直接的办法是用 std::unordered_set 记录已出现过的中间值。一旦某个平方和重复出现,说明开始循环,立刻返回 false。
示例逻辑:
int next(int n) {
int sum = 0;
while (n) {
int d = n % 10;
sum += d * d;
n /= 10;
}
return sum;
}
主判定函数里反复调用 next(),每次把结果存进 seen 集合,遇到 1 就返回 true,遇到重复就返回 false。
为什么不能只判断是否等于 1 或 4
有人记得“快乐数最终会到 1,非快乐数会掉进 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 这个环”,于是想只检查是否等于 4 来提前退出。这确实可行,但有风险:
- 这个循环是针对十进制、平方和规则推导出的,不是数学公理,依赖具体实现细节
- 若误写
next()(比如漏了某位、用了错误进制),可能跳过 4 却仍陷循环 - 不同语言或优化下,中间值可能先出现其他重复数(如 145 出现两次),仅判 4 会漏判
所以除非你明确限定输入范围并做过全覆盖验证,否则别省掉 unordered_set ——它更鲁棒,且现代 C++ 中插入/查找均摊 O(1),开销可忽略。
如何避免整型溢出或性能陷阱
int 类型完全够用:最大输入是 INT_MAX(约 2e9),其各位平方和最大为 9²×10 = 810(10 位全是 9),后续所有中间值都 ≤810。所以不会溢出,也不需要 long long。
但要注意两个实操细节:
- 别在循环里反复构造
std::string或用std::to_string拆位——字符串转换比取模慢好几倍 - 别用
std::set替代std::unordered_set:前者是红黑树,O(log n) 插入;后者哈希表,O(1) 平均,对这种小数值集合更合适 - 可以预分配
seen.reserve(100),因为实际循环长度最多二十几个数,减少 rehash
LeetCode 202 题常见报错和调试建议
提交时容易遇到 Time Limit Exceeded 或 Wrong Answer,多数源于这几个点:
-
next()函数没处理 n == 0 的边界:虽然输入是正整数,但中间值可能为 0(比如输入 10,第一次算得 1²+0²=1),但你的循环条件如果是while (n > 0)就没问题;若写成while (n)也 OK,C++ 中 0 为 false - 忘记在循环开始前把初始值
n加入seen:比如输入 1,不加的话会直接进循环体,算出 next(1)=1,然后发现 1 已存在 → 错判为非快乐数 - 使用
std::set但没写自定义比较,或用了std::map却只查 key 存在性却忘了用.count()或.find() != end()
调试时建议手动跑几个典型值:19(是快乐数)、2(不是)、100(是),打印每一步的 next() 结果,对照标准序列核对。
真正麻烦的不是算法本身,而是中间状态管理——记不住哪些数见过,或者记错了起始点,问题就藏得挺深。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











