Leetcode | 202. 快乐数
题目
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
示例:
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/happy-number
解题
思路
首先,我们需要注意到这个快乐数的寻找过程,其实是一个循环。那么我们就可以将这个问题转变为链表找环问题。
为什么会是一个循环,我们单纯来思考找快乐数的这一过程:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。这样我们的寻找过程会有三种情况:
- 寻找过程为:n -> n1 -> n2 -> … -> ni -> 1 -> 1… 这种情况是快乐数,最后变成1的循环。如题中示例19 -> 82 -> 68 -> 100 -> 1 -> 1 -> …
- 如果不是快乐数,这个数字的寻找过程可能会变成这样的一种循环: n -> n1 -> n2 -> … -> ni -> … -> nj-> ni -> … -> nj… 如下图例(图源于力扣官方题解)
- 也可能会变成越来越大的情况。
但经过证明可以发现不会出现越来越大的情况呢?可以看甜姨题解里给出的证明。或者可以这样想,当每位数都为最大时,他的平方和为多少,比如十三位数的最大值9999999999999的每位数平方和为1053,我们可以得出对于一个十三位数其最坏情况也会将其降低至四位数1053最终很可能在四位数之内进行循环,所以其实到最大的时候都会急速下降使其在某个范围内循环不会出现会越来越大的情况。
所以这个问题就变成了必然会形成一个环,找环的入口问题,如果最终环的入口为1那么为快乐数,环的入口不为1就不是快乐数。
想清楚这个问题,这道题的解题思路就和Leetcode | 141. 环形链表的找环思路一模一样了。
唯一不同的就是环形链表的下一个直接用node.next就可以找到,这道题的下一个需要写一个计算其每位数平方和的方法去找其下一个。
快慢指针找环解法(java)
参考甜姨题解。
public class Solution {
//计算每一次将该数替换为它每个位置上的数字的平方和的过程
public int squareSum(int n) {
int sum = 0;
while(n > 0){
//依次整除10将个位数提取出来,依次加上平方和,算出每位数的平方和的总和
int digit = n % 10;
sum += digit * digit;
n /= 10;
}
return sum;
}
public boolean isHappy(int n) {
//设置快慢指针,fast每次平方两步,slow每次平方一步,找到是否存在环的相遇点,因为不会无限放大
int slow = n, fast = squareSum(n);
while (slow != fast){
slow = squareSum(slow);
fast = squareSum(squareSum(fast));
};
return slow == 1;//环的相遇点是1的说明最终会变为快乐数
}
}
哈希表解法
public class Solution {
//计算每一次将该数替换为它每个位置上的数字的平方和的过程
public int squareSum(int n) {
int sum = 0;
while(n > 0){
//依次整除10将个位数提取出来,依次加上平方和,算出每位数的平方和的总和
int digit = n % 10;
sum += digit * digit;
n /= 10;
}
return sum;
}
public boolean isHappy(int n) {
//设置一个哈希表,存储每次的数,遇到重复的说明是环的入口
Set<Integer> numset = new HashSet<>();
while(!numset.contains(n)&&n!=1){
numset.add(n);
n = squareSum(n);
}
//判断环的入口是否为1
return n == 1;//环的相遇点是1的说明最终会变为快乐数
}
}