一日一练: 快乐数

108 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

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

暴力

拿到题目之后,很容易想到用while循环去暴力搜索,但是根据快乐数的第二条规则: 也可能是 **无限循环** 始终变不到 1,如果是始终变不到1,那么以 n === 1为终止条件的while循环就会陷入死循环。所以暴力枚举是无法解决这个问题的。

抽象成已知问题求解

分析一下:

  1. 如果是快乐数的话,那么结果肯定会变成1;
  2. 如果不是快乐数,n的值会怎么变化?会陷入循环?还是会一直变大,直至无穷大。

考察一下n的变化规率:

当前数字下一个数字下下个
52529
9916241
99924329
999932429
9999940541
.........
999999999729134
2^31 - 126040

从上表可以看出,第二轮最大是729,那么第三次的计算最大值肯定不会超过(三位最大数)999计算之后的值243,那么就可以排除上面说的如果不是快乐数,n会一直变大的情况。换句话说,如果n不是快乐数,多次计算之后,n会陷入无限循环,如果把无循环中的重复出现的值当做一个环,那么这个题目就可以抽象成 判断链表是否是环形链表。那么就可以使用快慢指针或者哈希表来解决。

// 快慢指针
function isHappy(n: number): boolean {
    if (n === 1) return true
    let slow = n
    let fast = getDigitSum(n)
    if (fast === 1) {
        return true
    }
    fast = getDigitSum(fast)
    while(slow !== 1 && fast !== 1 && fast !== slow) {
        slow = getDigitSum(slow)
        fast = getDigitSum(fast)
        if (fast === 1) {
            return true
        }
        fast = getDigitSum(fast)
    }
    // 如果快慢指针相遇,说明有在环中相遇,表明n陷入循环,不是快乐数
    return fast !== slow
};

// 工具函数,计算每个位置平方的总和
function getDigitSum(n: number) {
    let sum = 0
    while(n !== 0) {
        let num = n % 10
        sum += Math.pow(num, 2)
        n = (n / 10) | 0
    }
    return sum
}