[LeetCode] 202. 快乐数
1. 问题描述
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例 1:
输入:n = 19
输出:true
解释:12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:
输入:n = 2
输出:false
提示:
1 <= n <= 231 - 1
2. 实现思路
2.1 自己的思路
~~(一定要写出来啊喂)~
题目的含义比较简单,对数字每一位求平方和(各位平方和)然后看其是否为1,如果不是则将结果继续重复此操作即可
那么涉及到两个知识点,第一个是如何求各位平方和,这个比较基础惹,不断对10取模然后sum累加就行
第二个是如何判断循环终止,这里就需要发现规律了,比如第二个测试用例2,结果如下
2
2^2 = 4
4^2 = 16
1^2 + 6^2 = 37
3^2 + 7^2 = 58
5^2 + 8^2 = 89
8^2 + 9^2 = 145
1^2 + 4^2 + 5^2 = 42
4^2 + 2^2 = 20
2^2 + 0^2 = 4
...
可以发现操作进行到某处后,会得到之前求过的某个值,然后循环往复,又因为输入规模是在int以内,那么就可以用递归来实现了
/*
* @lc app=leetcode.cn id=202 lang=csharp
*
* [202] 快乐数
*/
// @lc code=start
public class Solution
{
// 哈希表来帮助判断是否已经求过某个数的各位平方和
private HashSet<int> _visited = new();
public bool IsHappy(int n)
{
// 终止条件①:1就是快乐数
if (n == 1) return true;
// 终止条件②:遇到已经求过各位平方和的情况,说明遇到循环,非快乐数
if (_visited.Contains(n)) return false;
_visited.Add(n);
return IsHappy(Sum(n));
}
private int Sum(int num)
{
int sum = 0;
while (num > 0)
{
sum += (num % 10) * (num % 10);
num /= 10;
}
return sum;
}
}
// @lc code=end
- 时间复杂度:O(log n)
对于计算单次Sum方法:要计算log10 n + 1次,时间复杂度O(log n)
对于递归:
由于快乐数的数学性质(如果n>243,则其各位平方和严格小与n),当 n≥243 时,会在常数步内急速衰减到243以下;当n<243时,最多迭代243次(因为整数最多243个)
那么总的时间复杂度就是 一个常数 * 单次O(log n)
最后结果O(log n)
- 空间复杂度:O(1)
由于上面提到的快乐数的数学性质,_visited最多存储几百个元素,不随n的位数增大而增大
2.2 题解思路
第202题. 快乐数 | 哈希法 | 集合 | 无限循环检测
先来看随想录的题解:
核心也是和刚刚分析的差不多,好耶~
题目中说了会 无限循环,那么也就是说**求和的过程中,sum会重复出现
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了
求各位平方和**
C++:
class Solution {
public:
// 取数值各个位上的单数之和
int getSum(int n) {
int sum = 0;
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> set;
while(1) {
int sum = getSum(n);
if (sum == 1) {
return true;
}
// 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}
};
- 时间复杂度: O(logn)
- 空间复杂度: O(logn)
C#:
public class Solution {
private int getSum(int n) {
int sum = 0;
//每位数的换算
while (n > 0) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
public bool IsHappy(int n) {
HashSet <int> set = new HashSet<int>();
while(n != 1 && !set.Contains(n)) { //判断避免循环
set.Add(n);
n = getSum(n);
}
return n == 1;
}
}
接下来再看看力扣的题解(如果是为了练习哈希表的话,其实这部分可以跳过惹)
考虑到如果输入规模比int更大呢?那么很显然我之前写的方法会爆调用栈,以及集合会变得非常大
通过观察数据规律可以发现,虽然代码里没有明显的链表结构,但是数据呈现出类似链表的结构

那么结合弗洛伊德判圈法,我们初始两个指针,快指针指向原数据第一次求各位平方和(假设求S(n)的方法名为GetNext(n))
快指针每次走两步,慢指针每次走一步
如果n是快乐数,那么快指针肯定会比慢指针先达到1
如果不是,那么快指针和慢指针肯定会在某个数相遇
class Solution {
public int getNext(int n) {
int totalSum = 0;
while (n > 0) {
int d = n % 10;
n = n / 10;
totalSum += d * d;
}
return totalSum;
}
public bool IsHappy(int n) {
int slowRunner = n;
int fastRunner = getNext(n);
while (fastRunner != 1 && slowRunner != fastRunner) {
slowRunner = getNext(slowRunner);
fastRunner = getNext(getNext(fastRunner));
}
return fastRunner == 1;
}
}
/*
作者:力扣官方题解
链接:https://leetcode.cn/problems/happy-number/solutions/224894/kuai-le-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
时间复杂度:O(log n)
空间复杂度:O(1)
2.3 优化(或者敲一遍题解?)
Floyd判圈法~
/*
* @lc app=leetcode.cn id=202 lang=csharp
*
* [202] 快乐数
*/
// @lc code=start
public class Solution
{
public bool IsHappy(int n)
{
int slow = n;
int fast = GetNext(n);
while (fast != 1 && slow != fast)
{
slow = GetNext(slow);
fast = GetNext(GetNext(fast));
}
return fast == 1;
}
private int GetNext(int num)
{
int result = 0;
while (num > 0)
{
result += (num % 10) * (num % 10);
num /= 10;
}
return result;
}
}
// @lc code=end
3. 补充知识と杂谈,总结
复习一下弗洛伊德判圈法吧
- 判圈
快慢指针,快指针每次2步,慢的每次1步,如果相遇则有圈
- 判圈的入口在哪
在上面相遇的基础后,将快慢指针中的一个放到开头,两个指针每次走1步,相遇即圈的入口

浙公网安备 33010602011771号