编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环
但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。如果 n 是快乐数就返回 True ;不是,则返回 False 。
示例:
输入:19 输出:true 解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02
= 1来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/happy-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
乍一看这题,直接函数递归就可以进行判断。唯一的问题就是可能陷入死循环,那就必须要引入判断机制(是否已经进入死循环)。
根据题目描述,可以预见较大的数能够在操作下快速减少到只有两三位数,也就是说,所有可能存在的“死循环”内的元素都是不超过3位数的,具体是多少呢?我们取999这个三位数,操作一次以后就是243,可以得出结论,死循环中的任意一个数都必然小于243.因此,可以对输入的数进行题目中的处理,当数字减小到小于243的时候就可以进行判断。
如何进行判断呢?那就是直接暴力遍历1-243的所有数字,判断其是否会进入死循环。因为相当于是打表,所以不需要考虑时间复杂度。判断进入死循环的标准就是调用判断次数超过243次。
目标是输出所有1-243中会进入死循环的数,结果发现会进入死循环的数字远比不会进入的多,所以转变成输出所有小于243的“快乐数”。代码如下:
#include
#include
int ishappy(int num,int count){
if (count >= 243) return 1;
if (num == 1) return 0;
int m = 0;
while(num != 0){
m += (num%10)*(num%10);
num /= 10;
}
return ishappy(m,count+1);
}
int main( ){
int k,count=0;
for (int i=0;i<243;i++){
k = 0;
k = ishappy(i,0);
if (k == 0) {
printf("%d,",i);
count++;
}
}
printf("\ncount=%d",count);//最后一位表示一共有多少个小于243的快乐数
return 0;
}
//输出结果是:
1,7,10,13,19,23,28,31,32,44,49,68,70,79,82,86,91,94,97,100,103,109,129,130,133,139,167,176,188,190,192,193,203,208,219,226,230,236,239,
count=39
那么就很简单了,对数字进行若干次递归操作,当数字小于243的时候判断它是否属于快乐数集和就可以了。
int a[39]={1,7,10,13,19,23,28,31,32,44,49,68,70,79,82,86,91,94,97,100,103,109,129,130,133,139,167,176,188,190,192,193,203,208,219,226,230,236,239};
bool isHappy(int n){
if (n>243){
int m=0;
while (n != 0){
m+=(n%10)*(n%10);
n/=10;
}
return isHappy(m);
}
for (int i = 0;i<39;i++){
if (n == a[i]) return true;
else if (n<a[i]) return false;
}
return false ;
}
很多地方存在优化空间,先看看效果。
熟悉我的小伙伴一定会知道,我对这个60%不到的运行结果肯定非常不满意,肯定会进行优化。在这个算法大致思路不变的前提下,优化空间有什么?
那么整容以后的代码如下:
int a[39]={1,7,10,13,19,23,28,31,32,44,49,68,70,79,82,86,91,94,97,100,103,109,129,130,133,139,167,176,188,190,192,193,203,208,219,226,230,236,239};
bool isHappy(int n){
while (n>243){
int m=0;
while (n != 0){
m+=(n%10)*(n%10);
n/=10;
}
n = m;
}
int left = 0,right = 38,mid;
while (left <= right){
mid = (left+right)/2;
if (n > a[mid]){
left = mid+1;
} else if (n < a[mid]){
right = mid-1;
} else if (n == a[mid]){
return true;
}
}
return false ;
}
运行结果:
直接就双百了。我还没把数组a放进函数体呢……
不愧是简单难度的题目。
我看了一下评论区,似乎官方题解给出了:只有一条会死循环的链。也就是说如果期待进入死循环,必然最后会进入这唯一一个环。
评论区有另外一位老哥的题解给我了很大的提示:(talk is cheap,give me the code):
bool isHappy(int n){
while(1){
if(n<10){
if(n==1||n==7)return true;
else return false;
}
int sum=0;
while(n!=0){
sum=sum+(n%10)*(n%10);
n=n/10;
}//while
n=sum;
}
}
//在小于10的数中只由1和7是快乐数
/*作者:hui-fei-de-xiao-hou
链接:https://leetcode-cn.com/problems/happy-number/solution/cyu-yan-by-hui-fei-de-xiao-hou/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。*/
那这就很好办了。这位老哥没有解释为什么循环最终结果一定会小于十,现在我来给出证明:(评论区有人问,我没绑定手机没有办法回答)
之前官方题解中已经告知了只会存在一条死循环链,而且该链中最小值是4,也就是意味着,如果出现死循环,必然会出现数字4,此数字就是小于10的。而如果不出现死循环,就一定会经过有限次操作下降到1.也就是说,“必然能够经过有限次操作让数字n小于10”.我觉得这个思路很可以!虽然结果并不能比我的打表方式好很多,但是这个思路很值得借鉴。
官方题解还给出了“hash表”“快慢指针”两种办法来解决这个问题。但是我的原则是:能快速打表解决的问题绝对不用花里胡哨的算法来解决。
这道题就到这里了,希望大家能点个小小的赞~