力扣 202 快乐数
编写一个算法来判断一个数 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
哈希法
这道题目可以使用哈希法来判断 sum 是否重复出现,如果重复了就 return false, 否则一直找,直到 sum 为 1 为止。而判断 sum 是否重复出现就可以使用 unordered_set 。
✅哈希法解题代码:
class Solution {
public:
int getSum(int n){
int sum = 0;
while(n != 0){
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int>set;
while(true){
int sum = getSum(n);
if(sum == 1){
return true;
}
if(set.find(sum) != set.end()){
return false;
}
else{
set.insert(sum);
}
n = sum;
}
}
};
这段代码大体可以分为 getSum 和 isHappy 两个函数:
getSum实现各数位平方求和功能isHappy判断是否为快乐数
关于 isHappy 函数的逻辑:
- 当
sum等于 1 时,表明这是符合条件的快乐数,返回true set.find(sum) != set.end()表示该sum已经出现过,也就说明会陷入无限循环,所以可以直接返回false。因为当find找不到要求元素时会返回set.end(),所以语句中不等于表示找到了该sum- 如果不等于 1 ,并且没有重复出现则存入 set 中以供下次查询
为什么用 while(true) 不用 for ? 核心原因是题目给出了存在无限循环的可能,所以不能用 for 循环
复杂度分析:
- 时间:每次数字位数不断缩减,近似 O (log n)
- 空间:最坏存储所有中间平方和,O (log n)
弗洛伊德判圈法
所谓判圈法就是快慢指针法,其底层逻辑与 环形链表Ⅱ 142 基本相同,相关算法证明与特性详见该文章。
本题可以使用该方法的原因是无论是否为快乐数最后都会成环,其逻辑如下:
- 是快乐数时,最终会迭代到 1,而 1 继续迭代还是 1,因此成自环
- 不是快乐数时,一定会遇到重复的数字,自此开始进入环循环
以快乐数19为例:
以非快乐数4为例:
由示例图可见,无论哪种情况最终都会成环,而根据算法 快指针一次“走两步”,慢指针一次“走一步”,成环必相遇 的特性,要判断是否为快乐数只需判断相遇时所在位置的值是否为1即可。
✅快慢指针法解题代码:
class Solution {
public:
// 计算数字每一位的平方和
int getSum(int n) {
int sum = 0;
while (n != 0) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
int slow = n;
int fast = getSum(n);
// 直到快慢指针相遇再退出循环
while (slow != fast) {
slow = getSum(slow); // 慢指针走1步
fast = getSum(getSum(fast)); // 快指针走2步
}
// 相遇后判断环是不是1的自环
return slow == 1;
}
};
复杂度分析:
- 时间复杂度:O (log n)
- 空间复杂度:O (1)
另外,常规快慢指针的写法应该是分情况讨论,当遇到 1 时表明是快乐数,直接返回 true,不是时再根据成环特性进行操作,这样做的好处在于当测试用例是快乐数时可以省略一部分判断,较为高效;而统一成环判断的好处在于可以统一思路,更为易懂。
关于哈希法与弗洛伊德判圈法: 哈希法的优点是更容易想到,也更容易理解;而弗洛伊德判圈法的好处在于不需要新开容器,空间复杂度上更高效。