快乐数 202

0 阅读4分钟

力扣 202 快乐数

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

「快乐数」 定义为:

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

如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例 1:

**输入:**n = 19 **输出:**true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1

示例 2:

**输入:**n = 2 **输出:**false

提示:

  • 1 <= n <= 231 - 1

哈希法

这道题目可以使用哈希法来判断 sum 是否重复出现,如果重复了就 return false, 否则一直找,直到 sum1 为止。而判断 sum 是否重复出现就可以使用 unordered_set

✅哈希法解题代码:

class Solution {
public:
    int getSum(int n){
        int sum = 0;
        while(n != 0){
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
        unordered_set<int>set;
        while(true){
            int sum = getSum(n);
            if(sum == 1){
                return true;
            }
            if(set.find(sum) != set.end()){
                return false;
            }
            else{
                set.insert(sum);
            }
            n = sum;
        }
    }
};

这段代码大体可以分为 getSumisHappy 两个函数

  • getSum 实现各数位平方求和功能
  • isHappy 判断是否为快乐数

关于 isHappy 函数的逻辑

  1. sum 等于 1 时,表明这是符合条件的快乐数,返回 true
  2. set.find(sum) != set.end() 表示该 sum 已经出现过,也就说明会陷入无限循环,所以可以直接返回 false。因为当 find 找不到要求元素时会返回 set.end(),所以语句中不等于表示找到了该 sum
  3. 如果不等于 1 ,并且没有重复出现则存入 set 中以供下次查询

为什么用 while(true) 不用 for ? 核心原因是题目给出了存在无限循环的可能,所以不能用 for 循环

复杂度分析:

  • 时间:每次数字位数不断缩减,近似 O (log n)
  • 空间:最坏存储所有中间平方和,O (log n)

弗洛伊德判圈法

所谓判圈法就是快慢指针法,其底层逻辑与 环形链表Ⅱ 142 基本相同,相关算法证明与特性详见该文章。

本题可以使用该方法的原因是无论是否为快乐数最后都会成环,其逻辑如下:

  • 是快乐数时,最终会迭代到 1,而 1 继续迭代还是 1,因此成自环
  • 不是快乐数时,一定会遇到重复的数字,自此开始进入环循环

快乐数19为例: 快乐数  202_20260728_102702.png非快乐数4为例: 快乐数  202_20260728_103126.png 由示例图可见,无论哪种情况最终都会成环,而根据算法 快指针一次“走两步”,慢指针一次“走一步”,成环必相遇 的特性,要判断是否为快乐数只需判断相遇时所在位置的值是否为1即可。

✅快慢指针法解题代码:

class Solution {
public:
    // 计算数字每一位的平方和
    int getSum(int n) {
        int sum = 0;
        while (n != 0) {
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
        int slow = n;
        int fast = getSum(n);
        // 直到快慢指针相遇再退出循环
        while (slow != fast) {
            slow = getSum(slow);               // 慢指针走1步
            fast = getSum(getSum(fast));       // 快指针走2步
        }
        // 相遇后判断环是不是1的自环
        return slow == 1;
    }
};

复杂度分析:

  • 时间复杂度:O (log n)
  • 空间复杂度:O (1)

另外,常规快慢指针的写法应该是分情况讨论,当遇到 1 时表明是快乐数,直接返回 true,不是时再根据成环特性进行操作,这样做的好处在于当测试用例是快乐数时可以省略一部分判断,较为高效;而统一成环判断的好处在于可以统一思路,更为易懂。

关于哈希法与弗洛伊德判圈法: 哈希法的优点是更容易想到,也更容易理解;而弗洛伊德判圈法的好处在于不需要新开容器,空间复杂度上更高效。