Appearance
实战:快乐数
这道题很有趣——用哈希表检测循环。
题目描述
LeetCode 202. 快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果最终为 1,就是快乐数。
示例 1:
输入:n = 19
输出:true
解释:
1² + 9² = 82
8² + 2² = 68
6² + 8² = 100
1² + 0² + 0² = 1 ✓示例 2:
输入:n = 2
输出:false
解释:会进入循环 2 → 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 → ...问题分析
核心问题:这个序列要么最终变成 1,要么进入循环。
如何检测循环?用哈希表记录出现过的数字,如果某个数字再次出现,说明进入循环。
辅助函数:计算各位数字平方和
javascript
function getNext(n) {
let sum = 0;
while (n > 0) {
const digit = n % 10;
sum += digit * digit;
n = Math.floor(n / 10);
}
return sum;
}执行过程:
n = 19
19 % 10 = 9 → 9² = 81
19 / 10 = 1
1 % 10 = 1 → 1² = 1
1 / 10 = 0
sum = 81 + 1 = 82解法一:哈希集合检测循环
javascript
function isHappy(n) {
const seen = new Set();
while (n !== 1 && !seen.has(n)) {
seen.add(n);
n = getNext(n);
}
return n === 1;
}
function getNext(n) {
let sum = 0;
while (n > 0) {
const digit = n % 10;
sum += digit * digit;
n = Math.floor(n / 10);
}
return sum;
}执行过程
n = 19
n=19: seen = {19}, n = 82
n=82: seen = {19, 82}, n = 68
n=68: seen = {19, 82, 68}, n = 100
n=100: seen = {..., 100}, n = 1
n === 1,return true复杂度
- 时间:取决于循环长度,最坏 O(log n × log n)
- 空间:O(log n),存储出现过的数字
解法二:快慢指针
这道题本质上是判断链表是否有环!
把每个数看作链表节点,getNext(n) 就是指向下一个节点的指针。用快慢指针检测环:
javascript
function isHappy(n) {
let slow = n;
let fast = getNext(n);
while (fast !== 1 && slow !== fast) {
slow = getNext(slow);
fast = getNext(getNext(fast));
}
return fast === 1;
}
function getNext(n) {
let sum = 0;
while (n > 0) {
const digit = n % 10;
sum += digit * digit;
n = Math.floor(n / 10);
}
return sum;
}- 如果 fast 先到达 1,是快乐数
- 如果 slow 和 fast 相遇,说明有环,不是快乐数
复杂度
- 时间:O(log n)
- 空间:O(1),不需要额外存储
为什么一定会循环或到达 1?
有人可能会问:会不会无限增长,永远不循环也不到达 1?
不会。因为对于任意 n 位数,各位数字平方和最大是 n × 81(每位都是 9)。
- 3 位数最大是 999,各位平方和最大 243
- 4 位数最大是 9999,各位平方和最大 324
所以数字会快速下降到一个有限范围内,最终要么到达 1,要么进入循环。
本章小结
快乐数展示了哈希表检测循环的应用:
- 问题抽象:数列变化 → 链表遍历
- 循环检测:用 Set 记录历史状态
- 空间优化:快慢指针可以 O(1) 空间检测环
这种"用哈希表检测循环"的思路,在很多问题中都有应用。