202. 快乐数
题目:编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
链接 https://leetcode.cn/problems/happy-number
个人思路
- 求取平方和:不断取余,然后进行平方相加;判断平方和temp是否为1,是则返回True,同时建立一个memory列表保存每一次的结果,判断temp是否在前面平方和中出现过,即判断是否会出现循环,写代码的过程中出现下面的情况:
memory = []
class Solution:
def isHappy(self, n: int) -> bool:
temp = 0
while n > 0:
temp = temp + (n % 10)**2
n = n // 10
if temp == 1:
return True
elif temp in memory:
return False
else:
memory.append(temp)
self.isHappy(temp)
自己测试的时候打印发现结果应该是对的,但却没有任何返回值
原因:该函数为递归函数,else中嵌套函数。 执行return满足条件if temp == 1不是最外面的循环,而是else self.isHappy(temp)中 执行的函数,因此return返回的值被返回到了else中,不会返回到最外面一层。
解决:在else中也增加return。
但还是出现了错误,后面有个用例没能通过
memory = []
class Solution:
def isHappy(self, n: int) -> bool:
temp = 0
while n > 0:
temp = temp + (n % 10)**2
n = n // 10
if temp == 1:
return True
elif temp in memory:
return False
else:
memory.append(temp)
return self.isHappy(temp)
因为存储temp是否在之前中出现过的列表memory在class外面,猜测是memory把前面其他用例计算过程中的temp也储存了下来,但不知道怎么修改:放在Solution后isHappy前会报错memory未定义,放在方法内则递归的时候每次都是空,也起不到作用。
正确的递归,在原有的方法内再新建一个方法:
class Solution:
def isHappy(self, n: int) -> bool:
memory = []
def happy(n):
temp = 0
while n > 0:
temp = temp + (n % 10)**2
n = n // 10
if temp == 1:
return True
elif temp in memory:
return False
else:
memory.append(temp)
return happy(temp)
return happy(n)
注:memory = set()这个更快
官方思路
- 用哈希集合检测循环
我们使用哈希集合而不是向量、列表或数组的原因是因为我们反复检查其中是否存在某数字。检查数字是否在哈希集合中需要 O(1) 的时间,而对于其他数据结构,则需要 O(n) 的时间。选择正确的数据结构是解决这些问题的关键部分
def isHappy(self, n: int) -> bool:
def get_next(n):
total_sum = 0
while n > 0:
n, digit = divmod(n, 10)
total_sum += digit ** 2
return total_sum
seen = set()
while n != 1 and n not in seen:
seen.add(n)
n = get_next(n)
return n == 1
这里的时间复杂度以及后面的思路分析有点意思,有兴趣的可以点击下面的链接自行查看
- 快慢指针法
(1)通过反复调用 getNext(n) 得到的链是一个隐式的链表。隐式意味着我们没有实际的链表节点和指针,但数据仍然形成链表结构。起始数字是链表的头 “节点”,链中的所有其他数字都是节点。next 指针是通过调用 getNext(n) 函数获得。
(2)意识到我们实际有个链表,那么这个问题就可以转换为检测一个链表是否有环。因此我们在这里可以使用弗洛伊德循环查找算法。这个算法是两个奔跑选手,一个跑的快,一个跑得慢。在龟兔赛跑的寓言中,跑的慢的称为 “乌龟”,跑得快的称为 “兔子”。
(3)不管乌龟和兔子在循环中从哪里开始,它们最终都会相遇。这是因为兔子每走一步就向乌龟靠近一个节点(在它们的移动方向上)。
(4)算法:我们不是只跟踪链表中的一个值,而是跟踪两个值,称为快跑者和慢跑者。在算法的每一步中,慢速在链表中前进 1 个节点,快跑者前进 2 个节点(对 getNext(n) 函数的嵌套调用)。
如果 n 是一个快乐数,即没有循环,那么快跑者最终会比慢跑者先到达数字 1。
如果 n 不是一个快乐的数字,那么最终快跑者和慢跑者将在同一个数字上相遇。
def isHappy(self, n: int) -> bool:
def get_next(number):
total_sum = 0
while number > 0:
number, digit = divmod(number, 10)
total_sum += digit ** 2
return total_sum
slow_runner = n
fast_runner = get_next(n)
while fast_runner != 1 and slow_runner != fast_runner:
slow_runner = get_next(slow_runner)
fast_runner = get_next(get_next(fast_runner))
return fast_runner == 1
一些思考:
① 本来认为这个解答有缺陷,因为快指针每次走两步,会漏掉走到1的一步,细看发现,漏掉的那一步如果为1,即get_next(fast_runner)为1,那么get_next(get_next(fast_runner))还是会为1,其实不影响
② 以为存在快指针刚好跳过慢指针从而恰好错过相遇,龟兔一直循环跑的情形,其实不然。在兔快要遇上龟时,有两种情况,兔与龟之间相差一个空位,这时,它们将在下一次走动相遇;如果相差两个空位,那么走动一次后将相差一个空位,即到了前面的情况。
时间复杂度:O(logn)
空间复杂度:O(1)
- 数学
(1)下一个值可能比自己大的最大数字是什么?根据我们之前的分析,我们知道它必须低于 243。因此,我们知道任何循环都必须包含小于 243 的数字,用这么小的数字,编写一个能找到所有周期的强力程序并不困难。
(2)如果这样做,您会发现只有一个循环:4→16→37→58→89→145→42→20→4。所有其他数字都在进入这个循环的链上,或者在进入 1 的链上。
(3)因此,我们可以硬编码一个包含这些数字的散列集,如果我们达到其中一个数字,那么我们就知道在循环中。
def isHappy(self, n: int) -> bool:
cycle_members = {4, 16, 37, 58, 89, 145, 42, 20}
def get_next(number):
total_sum = 0
while number > 0:
number, digit = divmod(number, 10)
total_sum += digit ** 2
return total_sum
while n != 1 and n not in cycle_members:
n = get_next(n)
return n == 1
这样的题目还是简单题,而且名字还叫快乐数,我哭死
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/happy-number/solution/kuai-le-shu-by-leetcode-solution/