题目描述
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
思路分析
根据题目意思,如果一个数按其规则变化最终可以为1则是快乐数,否则按规则变化会陷入无限循环。
所以我们需要判断在按规则变化数字时:
- 数字是否变为1。如果是,返回true
- 在这个过程中是否出现了循环。如果是,返回false
问题1:每次变化时,这个数每个位上的平方和如何得到?
有些同学在这个问题就会卡主,其实按照计算的规则来看,一个数需要通过变化多次,肯定需要用到迭代(循环)来计算的,这个是可以首先想到的;还有一个问题,由于数字在不断地变化,位数也不同,比如有时是2位数,有时是3位数,那么如何计算每个位上的数的平方和?
我们可以想到最直接的方法就是:例如数为n,循环将n对10取余,那么每次得到的余数就是数n从个位到高位的数值(可以在草稿纸上算算),那么数n的计算就是sum += n % 10 * (n % 10);,然后n /= 10;去进行下一个数字的计算。
代码实现:
public int getSum(int n) {
int sum =0;
while(n > 0) {
int digit = n % 10;//通过对10取余,获得当前数的最低位的数
sum += digit * digit;//求平方和
n = n/10;//迭代到更高一位,比如从个位到十位,十位到百位
}
return sum;
}
问题2:如何判断该过程是否出现了循环?
对于判断元素重复的问题,自然而然可以想到使用Set这种数据结构,它在存储每次计算得到的平方和之前,先判断当前的Set中是否已经有之前存储的数set.contains(n),如果有,那么循环形成,返回false,如果没有,则add到set中,直到得到的平方和为1,返回true。
根据题目的描述,这个循环一定会通过条件的判断返回。
最终,判断快乐数的这种算法的时间复杂度与空间复杂度都取决于在数字变换的过程中,会有多少个不同的数字出现。
代码实现:
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
while(n != 1 && !set.contains(n)) {
set.add(n);
n = getSum(n);
}
return n==1;
}
思考
通过反复调用 getSum(n) 得到的链是一个隐式的链表。隐式意味着我们没有实际的链表节点和指针,但数据仍然形成链表结构。起始数字是链表的头 “节点”,链中的所有其他数字都是节点。next 指针是通过调用 getSum(n) 函数获得。
意识到我们实际有个链表,那么这个问题就可以转换为检测一个链表是否有环。由此可想到,一般可以使用快慢指针来判断。getSum的方法不用改变,isHappy方法的代码简单的调整一下就可以
public boolean isHappy(int n) {
int slowPointer = n;//慢结点
int fastPointer = getSum(n);//把n计算一次后得到的平方和最为n的下一个节点
while(n != 1 && slowPointer != fastPointer){//当快指针没有追上慢指针并且n不为1
slowPointer = getSum(slowPoint);
fastPointer = getSum(fastPointer);
}
return n == 1;
}
备注:关于链表是否有环的判断和思路可以自行百度一下。这里就不分散说了