Easy | LeetCode 202. 快乐数 | 链表判环 | 数学
202. 快乐数
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果 可以变为 1,那么这个数就是快乐数。
如果 n
是快乐数就返回 true
;不是,则返回 false
。
示例 1:
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:
输入:n = 2
输出:false
提示:
1 <= n <= 231 - 1
解题思路
方法一: 暴力
直接暴力模拟这个计算过程, 不断使用每一位的平方和向前迭代, 如果迭代的结果是1, 说明是快乐数。如果迭代过程, 出现了循环, 则不是快乐数。判断是否出现循环的过程很简单, 就是使用Set把之前迭代的结果保存起来就可以了。
时间复杂度:这取决于从第一个数,迭代到第一次出现循环的迭代的次数。
空间复杂度:和时间复杂度一样。
public boolean isHappy(int n) {
Set<Integer> hasExists = new HashSet<>();
while (n != 1) {
if (hasExists.contains(n)) {
return false;
}
hasExists.add(n);
n = bitSquareSum(n);
}
return true;
}
public int bitSquareSum(int x) {
int sum = 0;
while (x != 0) {
int digit = x % 10;
sum += digit * digit;
x /= 10;
}
return sum;
}
方法二: 链表判环
在方法一的基础上做优化, 方法一中需要用Set来保存之前存储过的数字, 来判断是否出现环。其实判环的问题如 Easy | LeetCode 141. 环形链表 使用快慢指针就可以判定是否出现环。
此题的链表的无环出口就是位平方的结果是1。修改判断链表走到尽头的判断逻辑就可以对环形链表的代码进行改造
public boolean isHappy(int n) {
int slow = n, fast = n;
while (fast != 1) {
slow = bitSquareSum(slow);
fast = bitSquareSum(fast);
if (fast == 1) {
// 说明链表走到了尽头出口, 链表无环
return true;
}
fast = bitSquareSum(fast);
if (slow == fast) {
// 两指针相遇, 说明链表有环
return false;
}
}
// 快指针走到了链表尽头出口, 说明无环
return true;
}
public int bitSquareSum(int x) {
int sum = 0;
while (x != 0) {
int digit = x % 10;
sum += digit * digit;
x /= 10;
}
return sum;
}
方法三: 数学
对这个快乐数, 还有一个规律。虽然我没法证明, 但可以作为一种答案观赏。
\[4 \rightarrow 16 \rightarrow 37 \rightarrow 58 \rightarrow 89 \rightarrow 145 \rightarrow 42 \rightarrow 20 \rightarrow 4
\]
这一条循环路径, 是所有的非快乐数的必经之路。
利用这个规律, 只要判断到某个数走到了上面给的环的某个数字, 就是走到了环的入口, 开始进入环。既不需要用Set存储迭代的数字, 也不需要使用双指针, 直接判断就可。
public boolean isHappy(int n) {
Set<Integer> hasExists = new HashSet<>(Arrays.asList(4, 16, 37, 58, 89, 145, 42, 20));
while (n != 1 && !hasExists.contains(n)) {
n = bitSquareSum(n);
}
return n == 1;
}
public int bitSquareSum(int x) {
int sum = 0;
while (x != 0) {
int digit = x % 10;
sum += digit * digit;
x /= 10;
}
return sum;
}