Skip to content

Latest commit

 

History

History
122 lines (84 loc) · 5.71 KB

File metadata and controls

122 lines (84 loc) · 5.71 KB

202. 快乐数

Hi 大家好,我是张小猪。欢迎来到『宝宝也能看懂』系列特别篇 - 30-Day LeetCoding Challenge。

这是一个 leetcode 官方的小活动。可以在官网看到,从 4 月 1 号开始,每天官方会选出一道题,在 24 小时内完成即可获得一点小奖励。虽然奖励似乎也没什么用,不过作为一个官方的打卡活动,小猪还是来打一下卡吧,正好作为每天下班回家后的娱乐。

这里是 4 月 2 号的题,也是题目列表中的第 202 题 -- 『快乐数』

题目描述

编写一个算法来判断一个数是不是“快乐数”。

一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。

示例 1:

输入: 19
输出: true
解释:
1² + 9² = 82
8² + 2² = 68
6² + 8² = 100
1² + 0² + 0² = 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

这是个很直接的方案,我们可以通过一个集合来保存已经出现过的数字。这样对于每一个计算结果,我们只需要看它是否存在于集合中即可。如果存在,则触发失败退出;如果不存在,则添加进集合,并继续计算,直到我们得到了 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;
}

方案 2

我们重新回看整个计算过程,从数字 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;
}

方案 3

这个方案是一个比较偏数学的方案。简单的说就是,我们可以尝试罗列 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 难度的题什么时候会出现?下注下注,买定离手啦~

如果觉得不错的话,记得『三连』哦。小猪爱你们哟~

相关链接

我的微信公众号:张小猪粉鼻子