Hi 大家好,我是张小猪。欢迎来到『宝宝也能看懂』系列特别篇 - 30-Day LeetCoding Challenge。
这是一个 leetcode 官方的小活动。可以在官网看到,从 4 月 1 号开始,每天官方会选出一道题,在 24 小时内完成即可获得一点小奖励。虽然奖励似乎也没什么用,不过作为一个官方的打卡活动,小猪还是来打一下卡吧,正好作为每天下班回家后的娱乐。
这里是 4 月 2 号的题,也是题目列表中的第 202 题 -- 『快乐数』
编写一个算法来判断一个数是不是“快乐数”。
一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
示例 1:
输入: 19 输出: true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
EASY
题目的内容非常直白,读完题目之后小猪的第一反应也很直接,那就是我们需要根据一个数,找到符合题目要求的下一个数。即我们需要拿到这个十进制数的每一位数字,然后将它们的平方求和。
这个过程并不复杂,所以小猪这里就不卖关子啦,直接给出相关的函数实现:
const next = n => { let ret = 0; while (n !== 0) { ret += (n % 10) ** 2; n = (n / 10) >> 0; } return ret; }
这里面唯一一个需要注意的地方,可能就是这个看起来很奇怪的右移 0 位的操作。小猪这里只是用它来实现一个数字的取整。当然我们也可以用任何其它的取整方式来替换。
现在我们已经可以根据一个数找到符合要求的下一个数啦,那么如何判断它究竟是不是快乐呢?我们可以先想一下,判断快乐数的退出条件是什么。如果当得到 1,那么会触发成功的退出条件;如果当我们不断运算的过程中,发现得到了一个曾经出现过的数字,那么意味着这个过程会持续的循环下去,于是这时候应该触发失败的退出条件。
基于这个思路,下面给出 3 种方案,供小伙伴们参考。
这是个很直接的方案,我们可以通过一个集合来保存已经出现过的数字。这样对于每一个计算结果,我们只需要看它是否存在于集合中即可。如果存在,则触发失败退出;如果不存在,则添加进集合,并继续计算,直到我们得到了 1 为止。
这个方案是非常容易想到的,不过缺点是我们需要额外的空间来记录整个历史集合。具体代码如下:
const isHappy = (n) => { const history = new Set([n]); while (n !== 1) { n = next(n); if (history.has(n)) return false history.add(n); } return true; }
我们重新回看整个计算过程,从数字 N1 计算出下一个数字 N2,再计算出更下一个数字 N3,直到我们得到了 1 或者一个出现过的 Nx。这个过程连起来像不像是一种叫做链表的数据结构?而失败的条件正好就是链表包含环。所以这个问题可以直接套用判断链表是否包含环的思路来处理。
那么,处理这类问题,我们有一种经典的不需要额外空间的方式,那就是快慢指针。过程大概就是,维护一个一次移动一步的慢指针,和一个一次移动两步的快指针。如果包含环,那么快指针一定会在一个时刻实现对慢指针的套圈。
再返回这道题目,由于我们得到 1 以后,无论再怎么继续计算,结果始终是 1 。所以我们需要做的就是完成这个套圈时机的计算。最终再根据套圈后指针的值来判断究竟是因为 1 套圈,还是因为成环套圈即可。具体代码如下:
const isHappy = n => { let slow = n; let fast = next(n); while (slow !== fast) { slow = next(slow); fast = next(next(fast)); } return slow === 1; }
这个方案是一个比较偏数学的方案。简单的说就是,我们可以尝试罗列 1-9 这几个数字它们会产生的后续情况,最终会发现其实它们要么会成为快乐数,要么都会归结为固定的循环。而那些多余 1 位的数,最终也会变成某个以为的数字从而归结到这两种情况上来。所以我们只需要对这些特殊情况做判断即可。
这里如果感兴趣的小伙伴,可以直接去 wikipedia 搜索『Happy number』,即会看到详细的分析过程。
那出于对于传统文化的尊重(大雾),我们这里判断一个特殊情况,即如果这个数字不快乐,那么它一定会途径 4
这个特殊值(毕竟是个似乎很不吉利的数字)。基于此,我们可以直接得到下面这个简单的实现:
const isHappy = (n) => { while (n !== 1 && n !== 4) n = next(n); return n === 1; }
作为『30-Day LeetCoding Challenge』的第二题,仍旧是这么的和蔼可亲。小猪尝试给出了 3 种完全不同的思路,希望能帮到有需要的小伙伴。
不过小猪也十分好奇,MEDIUM 或者 HARD 难度的题什么时候会出现?下注下注,买定离手啦~
如果觉得不错的话,记得『三连』哦。小猪爱你们哟~