Algorithm
https://leetcode-cn.com/problems/happy-number/
编写一个算法来判断一个数是不是“快乐数”。
一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
示例:
输入: 19
输出: true
解释:
1
2
+
9
2
=
82
1^2 + 9^2 = 82
12+92=82
8
2
+
2
2
=
68
8^2 + 2^2 = 68
82+22=68
6
2
+
8
2
=
100
6^2 + 8^2 = 100
62+82=100
1
2
+
0
2
+
0
2
=
1
1^2 + 0^2 + 0^2 = 1
12+02+02=1
思路1:
这道题定义了一种快乐数,就是说对于某一个正整数,如果对其各个位上的数字分别平方,然后再加起来得到一个新的数字,再进行同样的操作,如果最终结果变成了1,则说明是快乐数,如果一直循环但不是1的话,就不是快乐数,那么现在任意给我们一个正整数,让我们判断这个数是不是快乐数,题目中给的例子19是快乐数,那么我们来看一个不是快乐数的情况,比如数字11有如下的计算过程:
1^2 + 1^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
我们发现在算到最后时数字4又出现了,那么之后的数字又都会重复之前的顺序,这个循环中不包含1,那么数字11不是一个快乐数,发现了规律后就要考虑怎么用代码来实现,我们可以用 HashSet 来记录所有出现过的数字,然后每出现一个新数字,在 HashSet 中查找看是否存在,若不存在则加入表中,若存在则跳出循环,并且判断此数是否为1,若为1返回true,不为1返回false,代码如下:
class Solution {
public:
bool isHappy(int n) {
unordered_set<int> st;
while (n != 1) {
int sum = 0;
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
n = sum;
if (st.count(n)) break;
st.insert(n);
}
return n == 1;
}
};
思路二:
这个单链表快慢指针讲很多了,可以直接跳过,去看看算法规模分析吧!
泛化链表结构:如果我们 把链表的节点看成问题中的状态 的话,它可以代表问题中的一个数字、一个阶段等等,是很泛化的东西。而链表的唯一指向关系它又代表什么呢?它其实代表着 状态与状态之间是唯一确定转换 的。也就是说从当前状态是唯一确定转换到下一个状态的。显然快乐数的转换规则完美的符合了这个特性。
我们根据题目的意思就将 1 作为链表的结尾即 NULL。
那么在单链表中一直走不到空地址意味着什么?就意味着链表有环呗。那么这个快乐数问题就被抽象为链表中是否有环的问题。即,如果这个链表有环那么就不是快乐数,如果链表没环,能指到空地址 1 的话那就说明这个数就是快乐数。
从上面我们得到了解决这个问题的算法思维,但是会不会出现这个链表太长了,有上个几千、几万、几十万的链表单元,影响我们找不到结果呢?我们来考虑一下这个算法的规模。
首先,如果输入值为 int,那么能知道 int 最多也就是一个以 2 开头的 10 位的数字。接着我们来考虑这样一个问题,在 int 数据范围中,哪一个数字 n 它所对应的下一个数字 n 是最大的?
我们能构造得到 1 999 999 999,那么只有构造出 1 个 1 ,9 个 9 的数字在 int 范围内就是最大的,那么下一个节点时多少呢?根据快乐数的定义能得到 9^2 \times9 + 1 = 7309
2
×9+1=730,那么 730 就是在整形范围之内任何一个数字所能映射到的下一个数字都不会超过 730,也就意味着当前所抽象出来的链表结构中节点数目最多不会超过 730 个,如果快指针一次走两步、慢指针一次走一步的话,那么慢指针走的最多,也只不过走了 731\times2 = 1462731×2=1462 步,而快指针就是走了 731731 步。
所以至此就证明完了,在整形快乐数中进行单链表判环的操作的话,操作步骤是有限的,就取个整吧,最多最多也就 2000 步了。所以这个方案是高效可行的。
使用“快慢指针”思想找出循环:“快指针”每次走两步,“慢指针”每次走一步,当二者相等时,即为一个循环周期。此时,判断是不是因为1引起的循环,是的话就是快乐数,否则不是快乐数。
注意:此题不建议用集合记录每次的计算结果来判断是否进入循环,因为这个集合可能大到无法存储;另外,也不建议使用递归,同理,如果递归层次较深,会直接导致调用栈崩溃。不要因为这个题目给出的整数是int型而投机取巧。
就像跑步一样,快乐数跑的是100m冲刺,非快乐数则是在1个400米的环形跑道里无休止的跑下去。
快慢指针就像是专业运动员和业务运动员,在100m的跑道上,两个都会跑到1. 而在环形跑道上,两个指针虽然见面了,但是估计慢指针才跑完一圈,快指针已经跑完两圈了
class Solution {
public:
bool isHappy(int n) {
int slow = n, fast = n;
while(true){
slow = findNext(slow);
fast = findNext(fast);
fast = findNext(fast);
if(slow == fast)
break;
}
return slow == 1;
}
int findNext(int n){
int res = 0;
while(n>0){
res += (n%10) * (n%10);
n /= 10;
}
return res;
}
};