文章目录
LC202 快乐数
1.读题
编写一个算法来判断一个数 n 是不是快乐数。
快乐数的定义——
- 对于一个正整数 每一次将该数替换为它每个位置上的数字的平方和
- 重复这个过程直到这个正整数变为1 (也可能是无限循环 但始终变不到1)
- 如果可以变为1 那么这个数就是快乐数
如果 n 是快乐数就返回 true
;不是,则返回 false
。
2.题解
【1】解题思路
思路一 哈希集合检测循环
本题中我们需要一步一步地判断是否平方和为1 且 需要一边判断一边查重~
怎么样 是不是自然而然地想到了链表 想到了“哈希集合” ~
来看看官方题解的方法一——
利用哈希集合检测循环
总结一下 最终的平方和的结果可能性——
1.最终得到1
2.最终进入循环 被查重
3.平方和越来越大 最后接近无穷大
其中最难检测和处理的就是第三种情况。。
代码中到底要不要管第三种情况呢??
我们单独把第三部分拎出来说道说道
如何判断平方和的最终值是否接近无穷大
依旧是参考官方题解的解释——
【1】思考一下 n位最大数的平方和为多少?
【2】这就很有意思了!
可以看到 是三位数的巅峰——999 取每一位的平方和之后得到的值不增反减
这意味着 三位数的数字是不可能大于243的
【3】在不断求每位数字的平方和的过程中
这个数——
要不就跌倒1
要不就在1-243之间循环
这个过程不会是无限的!他总会回到一个数!
【4】综上所述 第三种情况是永远不会发生的!
直接排除hhh
思路二 快慢指针法
快慢指针 解决循环问题yyds!!!!!
利用快慢指针思想找出循环是万能方法!
其实思想1和思想2大致的思想都是差不多的
都是判断n是否为1或者n是否是遍历过的节点 然后得到是否为快乐数
区别在于思想二利用快慢指针找到“遍历过的节点” 不用集合记录每次的计算结果 规避了集合超大到无法存储的情况(如果给出的n不是整数型就gg了鸭)
- 第一步
省略n步
- 最后一步 快指针和慢指针重合
判定链表中存在循环~
非快乐数!!!
【2】代码逻辑
思路一
经过上面的分析 我们得知 分两种情况就OK了~
最终结果为1
最终被查重
开始写代码!
1.获得“各位置数字平方之和”的函数getNext()
的完成
【1】利用一个while循环就OK啦~
【2】最后别忘了返回最终结果val
这里真的是梦回大一程序设计与基础hhh
2.判断输入的n
是否为快乐数的函数isHappy()
【1】先创建一个集合并进行初始化
【2】进入while循环 直到 确定n为快乐数/不是快乐数 为止
【3】循环中不断进行不符合节点插入哈希表&计算各位置数字平方之和
的操作
【4】跳出while循环 最终返回 n==1
【5】bool型函数 isHappy会给你答案~
True 或者 False
思路二
1.部分是完全相同的
2.部分使用了快慢指针的思想
回头一定要多复现几次这种思想! 涉及到循环的题都能用!
我们跟踪链表中的两个值
算法的每一步 慢数在链表中前进一个节点 快数前进两个节点
【1】初始化快慢数为输入数
【2】循环中 慢数变化一次 快数变化两次
【3】如果n为快乐数 那么没有循环 快数会比慢数先到达数字1
【3’】n不是快乐数 有循环 那么快数将和慢数在链表中的同一个数字上相遇
这样做 可以解决哈希集合过大的问题! 会节约很多内存!
3.C++代码
思路一
这注释的量都快比代码多辽
能看得懂8~
class Solution {
public:
int getNext(int n){
int val = 0;
while(n != 0)//其实while(n)就ok 这样写清晰点hh——当n不为0的时候 一直进行平方和的求和
{
int middle = n % 10;//每一位的那个数字
val += middle * middle;
n /= 10;
}
return val;//返回计算得到的“各位置数字平方之和”
}
bool isHappy(int n) {
unordered_set<int> record; //创建集合容器unordered_set 即为哈希表 命名为record
while(n!=1 && !record.count(n)) { //n不等于1 且 没有查重成功(record.count(n)不等于1)就一直循环
record.insert(n); //向哈希表中加入遍历到的这个节点
n = getNext(n);
}
return n == 1;//满足其中一项条件(是快乐数 / 不是快乐数) 退出循环
//如果最终的n为1的话 布尔型函数isHappy(n == 1)将返回 True
//如果最终的n只是因为查重成功退出 布尔型函数isHappy将返回 False
}
};
思路二
class Solution {
public:
int getNext(int n){
int val = 0;
while(n != 0)//其实while(n)就ok 这样写清晰点hh——当n不为0的时候 一直进行平方和的求和
{
int middle = n % 10;//每一位的那个数字
val += middle * middle;
n /= 10;
}
return val;//返回计算得到的“各位置数字平方之和”
}
bool isHappy(int n) {
int slow = n, fast = n;
do {
slow = getNext(slow);
fast = getNext(fast);
fast = getNext(fast);
}
while (slow != fast);//快指针没追上慢指针之前 一直重复计算各位置数字平方之和
return slow == 1;
}
};
可以看到内存消耗是大大减少的~
4.Java代码
思路一
class Solution {
private int getNext(int n) {
int val = 0;
while(n > 0) {
int x = n % 10;
val += x*x;
n /= 10;
}
return val;
}
public boolean isHappy(int n) {
Set<Integer> seen = new HashSet<>();
while (n != 1 && !seen.contains(n))//n不等于1 且 没有查重成功(seen.contains(n)不等于1)就一直循环
{
seen.add(n);
n = getNext(n);
}
return n == 1;
}
}
思路二
class Solution {
public int getNext(int n) {
int totalSum = 0;
while (n > 0) {
int d = n % 10;
n = n / 10;
totalSum += d * d;
}
return totalSum;
}
public boolean isHappy(int n) {
int slowRunner = n;
int fastRunner = getNext(n);
while (fastRunner != 1 && slowRunner != fastRunner) {
slowRunner = getNext(slowRunner);
fastRunner = getNext(getNext(fastRunner));
}
return fastRunner == 1;
}
}
依旧是明显提升的性能呢!