「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」
每日3天分钟,快速学算法
算法体能训练计划 - 第一周
题目地址 - 202. 快乐数
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果 可以变为 1,那么这个数就是快乐数。
- 如果 n 是快乐数就返回
true
;不是,则返回false
。
19 -> 82 -> 68 -> 100 -> 1
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1
思路
- 题目可以转化为,判断一个链表是否有环。
- 如果遍历某个节点为1,说明没环,就是快乐数。
- 如果遍历到重复的节点值,说明有环,就不是快乐数。
思考
Q: 会不会数字越来越大?
位数 | 最大值 | 下一个数 |
---|---|---|
1 | 9 | 81 |
2 | 99 | 162 |
3 | 999 | 243 |
4 | 9999 | 324 |
A: x -> x^2
什么时候最大, 199999999...999
,81*9+1 = 730
,所以最大也是730
,不会越来越大
背景:
c++中整型的范围是: -2 147 483 648 〜+2 147 483 647,所以符合上面最大的数是1 999 999 999,它的每项平方和是 81 * 9 + 1 = 730,意思说平方和最大就是730了,最多有730个节点。
解答
set集合判断是否有环
一直查找下一个快乐数并记录到set, 如果发现重复的数则不是快乐数,反之快乐数成立。
- 如果它不在Set集合中,就应该添加它
- 如果在Set集合中,说明已经开始循环了,那么它就不是快乐数
图解
// 获取下一个快乐数
const getNext = n => {
let total = 0
while(n > 0) {
total += Math.pow(n % 10, 2)
n = ~~(n / 10)
}
return total
}
const isHappy = n => {
const visited = new Set()
while (n !== 1 && getNext(n) !== 1) {
// 如果重复说明不是快乐数
if (visited.has(n)) {
return false
}
visited.add(n)
n = getNext(n)
}
// 到达这里说明不重复
return true
}
复制代码
检查元素是否在Set集合: O(1)
时间复杂度: O(logn)
快慢指针
快指针比慢指针每次多走一步,如果有环肯定会相遇,不相遇则是快乐数。
图解
const getNext = n => String(n).split('').reduce((pre, cur) => pre + Math.pow(cur, 2), 0)
const isHappy = n => {
let fast = n
let slow = n
while(fast !== 1 && getNext(fast) !== 1) {
fast = getNext(getNext(fast))
slow = getNext(slow)
// 到达1,则是快乐数
if (fast === 1) {
return true
}
if (fast === slow) {
return false
}
}
return true
}
复制代码