[leetcode日记]202.快乐数

题目

编写一个算法来判断一个数 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 <stdio.h>
#include <stdlib.h>


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%不到的运行结果肯定非常不满意,肯定会进行优化。在这个算法大致思路不变的前提下,优化空间有什么?

  1. 在数组a中查找时可以采用二分查找
  2. 将递归函数写成循环函数(最近学了编译原理,知道了函数递归开销会远大于循环的开销)
  3. 将数组a定义在函数体里。(也是学了编译原理,外部全局变量定义在堆里,函数临时变量定义在栈里,栈比堆操作速度要快)

那么整容以后的代码如下:

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表”“快慢指针”两种办法来解决这个问题。但是我的原则是:能快速打表解决的问题绝对不用花里胡哨的算法来解决。

这道题就到这里了,希望大家能点个小小的赞~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邵政道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值