202.快乐数
编写一个算法来判断一个数n
是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为1
,也可能是无限循环但始终变不到 1
。如果可以变为1
,那么这个数就是快乐数。
示例:
输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
如果 n 是快乐数就返回 True
;不是,则返回 False
。
题意分析
先来分析一下题意,本题要求我们判断一个数是否为快乐数,就是将这个数的个位、十位、百位、千位……的平方和求出来判断是否等于1,不等于的话进入下一次求和的过程。而某个数是否是快乐书的关键就是在对该数上的各位求平方和的时候不能陷入无限循环,而我们最直接的也最容易想到的方法就是将每一次求得的平方和储存起来,每次求得一个新的平方和的时候再对其进行查询,判断是否进入了死循环(即出现重复元素)。
HashSet法
如果我们用最简单数组和链表来存储,那每次搜索数组的代价是
O
(
n
)
O(n)
O(n),这个代价太大了;仔细观察一下,该存储结构内存储的元素是无序的、不重复的,并且要求最高的查找效率。那我们应该马上就想到用HashSet
来存储这些元素,因为它内部的元素是无序的,并且查找效率为
O
(
1
)
O(1)
O(1)。
代码:
//使用hashset检测循环,再C++中是unordered_set
class Solution{
public:
//用来获取n的各位的平方和
static int getNext(int n) {
int totalSum = 0;
while(n > 0) {
int d = n % 10;
n = n / 10;
totalSum += d * d;
}
return totalSum;
}
//判断是否是快乐数
bool isHappy(int n){
unordered_set<int> members;
while(n != 1 && members.find(n) == members.end()){
members.insert(n);
n = getNext(n);
}
return n == 1;
}
};
上面只介绍了一种方法,并且这种方法是利用了内置数据结构的特性,所以并不能算作高明。
接下来的这两种解法,才是最令人惊叹的。
快慢指针法
接着上面的问题,如果一个数是快乐数,那么它必然是要退出循环并且最终等于1
的,如果它不是,那么必然会进入一个无限循环,所以我们在这里引入一个快慢指针的概念。快慢指针,顾名思义即有两个指针一块一慢,一个在前面跑(步长大),一个在后面赶(步长小),如果我们将无限循环看作一个环(读者不妨模拟一遍,它确实是一个环),那么快指针和慢指针总能相遇;而一个数是快乐数的情况下,快指针总能最先到达1
。
代码:
快慢指针法
class Solution{
public:
static int getNext(int n) {
int totalSum = 0;
while(n > 0) {
int d = n % 10;
n = n / 10;
totalSum += d * d;
}
return totalSum;
}
bool isHappy(int n) {
int slowRunner = n;
int fastRunner = getNext(n);
while(fastRunner != 1 && fastRunner != slowRunner) {
//慢指针设置步长为1,通过的对getNext()函数的单次调用来实现
slowRunner = getNext(slowRunner);
//快指针设置步长为2,通过对getNext()函数的嵌套调用来实现
fastRunner = getNext(getNext(fastRunner));
}
return fastRunner == 1;
}
};
纯数学方法
如果读者听我上面讲的,对一定范围内的数n进行模拟之后便会发现,这些数最后陷入的无限循环都是一样的,即
4
−
>
16
−
>
37
−
>
58
−
>
89
−
>
145
−
>
42
−
>
20
−
>
4
4->16->37->58->89->145->42->20->4
4−>16−>37−>58−>89−>145−>42−>20−>4,根据这个规律,我们可以写出非常简洁的代码,同样要使用到HashSet
来存储这些无限循环数。
//数学方法1
class Solution{
public:
static int getNext(int n) {
int totalSum = 0;
while(n > 0) {
int d = n % 10;
n = n / 10;
totalSum += d * d;
}
return totalSum;
}
bool isHappy(int n) {
unordered_set<int> cycleMembers({4, 16, 37, 58, 89, 145, 42, 20});
while(n != 1 && cycleMembers.find(n) == cycleMembers.end()){
n = getNext(n);
}
return n == 1;
}
};
进一步的,你也可以存储所有这些无限循环数,你只需要存储一位即可(例如4
)。
//数学方法2
class Solution{
public:
static int getNext(int n) {
int totalSum = 0;
while(n > 0) {
int d = n % 10;
n = n / 10;
totalSum += d * d;
}
return totalSum;
}
bool isHappy(int n) {
while(n != 1 && n != 4){
n = getNext(n);
}
return n == 1;
}
};
整个题目就做完了,我也是参考了LeetCode官方题解,这里面讲的很详细,我从这里面学到了快慢指针这种方法,按照我的理解来说这题标答也应该是快慢指针法。