[数学规律] 202. 快乐数(寻找数学规律、判断是否无限循环:Set、快慢指针法)
202. 快乐数
题目链接:https://leetcode-cn.com/problems/happy-number/
关键点(分类):
- 数学规律、问题转化(找快乐数 → 判断是否无限循环)
- 判断是否无限循环:HashSet、快慢指针法;
题目分析:(难度不止简单)
本题中,可以分为两部分:计算n的每一位的平方和;判断是否是快乐数。
关键问题在于,如何判断一个数不是快乐数,这里把问题转化为:
判断一个数是否会陷入计算平方和的无限循环,如果会陷入无限循环,就说明该数不是快乐数,
所以,问题转化为判断计算平方和会不会出现无限循环。
- 思路1采用的方法是使用set存放每个计算过的平方和,一旦新得到的平方和存在于set中,说明出现循环。(关键在于空间不会无穷大的证明)
- 思路2采用的方法是快慢指针法,模仿链表判断链表是否有环的做法,空间复杂度为O(1)
所以,本题的难点在于:
- set暴力解法证明空间不会溢出;
- 模仿链表找环使用快慢指针判断是否出现循环(很难想到);
思路1:set 暴力解法
不断取n的每一位计算平方和,然后拿平方和的每一位继续计算平方和,直到n==1,说明这个数是快乐数。
如何判断一个数是否陷入无限循环?
一个数如果计算平方和时陷入无限循环,说明该数不是快乐数,那么要如何判断n的计算过程是否陷入了无限循环?
如果n == 之前得到过的平方和,说明陷入了无限循环,n不是快乐数。
所以需要一个HashSet,存放每一个计算得到的平方和,当n计算的平方和不是1且存在于set中,则返回false。
但是,有没有可能n在计算平方和时陷入无限循环,且每个平方和都未曾出现过,导致占用的存储空间无限大?
结论:当n为int型时,并不会出现每个平方和都未出现过而导致存储空间无限大。
证明:(参考:https://leetcode-cn.com/problems/happy-number/solution/kuai-le-shu-by-leetcode-solution/)
n有1位,则n最大是9, 每一位的最大平方和为9;
n有2位,则n最大是99,每一位的最大平方和是9^2+9^2=162
n有3位,则n最大是999,每一位的最大平方和是9^2+9^2+9^2=243
n有4位,则n最大是9999,每一位的最大平方和是9^2+9^2+9^2+9^2=324
...
n有13位,则n最大是9999999999999,每一位的最大平方和是1053
可以发现,n的位数达到int型能取的最大位数时,就算每一位都取最大值9,得到的平方和也只有1053,说明就算出现无限循环,也只会在1053以内的数字出现,同时n的下一个数的取值也被大大缩小,变为n有4位的情况,就算n每一位都取最大值9,得到的平方和也只有243,说明就算出现无限循环,也只会在243以内的数字里出现。
所以set并不会出现占用无穷大空间的情况。
实现代码
class Solution {
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();//存放每个出现过的n
while(n != 1){
//如果n曾经出现过,说明会出现循环,返回false
if(set.contains(n)) return false;
//如果n未曾出现过,则加入set
set.add(n);
//把n更新为每一位的平方和
n = caculate(n);
}
return true;
}
//计算n的每一位的平方和
public int caculate(int n){
int res = 0;
while(n != 0){
int bit = n % 10;//取当前最低位
res += bit * bit;
n = n / 10;
}
return res;
}
}
思路2:快慢指针法(模仿链表找环,更巧妙)
判断一个数在计算平方和时是否会陷入无限循环,就是判断该平方和是否曾经出现过。在链表中等价为判断一个链表中是否有环,因为有环在遍历过程中就会遇到曾经访问过的节点。
链表中判断是否有环,使用的是快慢指针法,设置一个快指针一次走两步,慢指针一次走一步,如果有环,则这两个指针一定会相遇。
如何把链表的快慢指针法应用到这题上?
快指针一次走两步,相当于一次计算两次平方和:先计算一个平方和,在该平方和的基础上继续计算平方和。
慢指针一次走一步,相当于一次计算一次平方和。
当两个过程得到相同的计算结果时,说明遇到曾经出现的数,出现循环,返回false;
如果其中一个过程得到1,则返回true.
实现代码
class Solution {
public boolean isHappy(int n) {
int fast = n, slow = n;
while(fast != 1 && slow != 1){
//快指针计算两次平方和
fast = caculate(fast);
fast = caculate(fast);
//慢指针计算一次平方和
slow = caculate(slow);
if(fast == slow && fast != 1) return false;
}
return true;
}
//计算n的每一位的平方和
public int caculate(int n){
int res = 0;
while(n != 0){
int bit = n % 10;//取当前最低位
res += bit * bit;
n = n / 10;
}
return res;
}
}