[LeetCode] 202. 快乐数

Notion页面

1. 问题描述

202. 快乐数 - 力扣(LeetCode)

编写一个算法来判断一个数 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

2. 实现思路

2.1 自己的思路

~~(一定要写出来啊喂)~

题目的含义比较简单,对数字每一位求平方和(各位平方和)然后看其是否为1,如果不是则将结果继续重复此操作即可

那么涉及到两个知识点,第一个是如何求各位平方和,这个比较基础惹,不断对10取模然后sum累加就行

第二个是如何判断循环终止,这里就需要发现规律了,比如第二个测试用例2,结果如下

2
2^2 = 4
4^2 = 16
1^2 + 6^2 = 37
3^2 + 7^2 = 58
5^2 + 8^2 = 89
8^2 + 9^2 = 145
1^2 + 4^2 + 5^2 = 42
4^2 + 2^2 = 20
2^2 + 0^2 = 4
...

可以发现操作进行到某处后,会得到之前求过的某个值,然后循环往复,又因为输入规模是在int以内,那么就可以用递归来实现了

/*
 * @lc app=leetcode.cn id=202 lang=csharp
 *
 * [202] 快乐数
 */

// @lc code=start
public class Solution
{
    // 哈希表来帮助判断是否已经求过某个数的各位平方和
    private HashSet<int> _visited = new();

    public bool IsHappy(int n)
    {
        // 终止条件①:1就是快乐数
        if (n == 1) return true;
        // 终止条件②:遇到已经求过各位平方和的情况,说明遇到循环,非快乐数
        if (_visited.Contains(n)) return false;

        _visited.Add(n);
        return IsHappy(Sum(n));
    }
    
    private int Sum(int num)
    {
        int sum = 0;
        while (num > 0)
        {
            sum += (num % 10) * (num % 10);
            num /= 10;
        }
        return sum;
    }
}
// @lc code=end

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

对于计算单次Sum方法:要计算log10 n + 1次,时间复杂度O(log n)

对于递归:

由于快乐数的数学性质(如果n>243,则其各位平方和严格小与n),当 n≥243 时,会在常数步内急速衰减到243以下;当n<243时,最多迭代243次(因为整数最多243个)

那么总的时间复杂度就是 一个常数 * 单次O(log n)

最后结果O(log n)

  • 空间复杂度:O(1)

由于上面提到的快乐数的数学性质,_visited最多存储几百个元素,不随n的位数增大而增大

2.2 题解思路

第202题. 快乐数 | 哈希法 | 集合 | 无限循环检测

先来看随想录的题解:

核心也是和刚刚分析的差不多,好耶~

题目中说了会 无限循环,那么也就是说**求和的过程中,sum会重复出现

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了

求各位平方和**

C++:

class Solution {
public:
    // 取数值各个位上的单数之和
    int getSum(int n) {
        int sum = 0;
        while (n) {
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
        unordered_set<int> set;
        while(1) {
            int sum = getSum(n);
            if (sum == 1) {
                return true;
            }
            // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
            if (set.find(sum) != set.end()) {
                return false;
            } else {
                set.insert(sum);
            }
            n = sum;
        }
    }
};
  • 时间复杂度: O(logn)
  • 空间复杂度: O(logn)

C#:

public class Solution {
    private int getSum(int n) {
        int sum = 0;
        //每位数的换算
        while (n > 0) {
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
    public bool IsHappy(int n) {
        HashSet <int> set = new HashSet<int>();
        while(n != 1 && !set.Contains(n)) { //判断避免循环
            set.Add(n);
            n = getSum(n);
        }
        return n == 1;
    }
}

接下来再看看力扣的题解(如果是为了练习哈希表的话,其实这部分可以跳过惹)

202. 快乐数 - 力扣(LeetCode)

考虑到如果输入规模比int更大呢?那么很显然我之前写的方法会爆调用栈,以及集合会变得非常大

通过观察数据规律可以发现,虽然代码里没有明显的链表结构,但是数据呈现出类似链表的结构

img

那么结合弗洛伊德判圈法,我们初始两个指针,快指针指向原数据第一次求各位平方和(假设求S(n)的方法名为GetNext(n))

快指针每次走两步,慢指针每次走一步

如果n是快乐数,那么快指针肯定会比慢指针先达到1

如果不是,那么快指针和慢指针肯定会在某个数相遇

class Solution {
    public int getNext(int n) {
        int totalSum = 0;
        while (n > 0) {
            int d = n % 10;
            n = n / 10;
            totalSum += d * d;
        }
        return totalSum;
    }

    public bool IsHappy(int n) {
        int slowRunner = n;
        int fastRunner = getNext(n);
        while (fastRunner != 1 && slowRunner != fastRunner) {
            slowRunner = getNext(slowRunner);
            fastRunner = getNext(getNext(fastRunner));
        }
        return fastRunner == 1;
    }
}
/*
作者:力扣官方题解
链接:https://leetcode.cn/problems/happy-number/solutions/224894/kuai-le-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/

时间复杂度:O(log n)

空间复杂度:O(1)

2.3 优化(或者敲一遍题解?)

Floyd判圈法~

/*
 * @lc app=leetcode.cn id=202 lang=csharp
 *
 * [202] 快乐数
 */

// @lc code=start
public class Solution
{
    public bool IsHappy(int n)
    {
        int slow = n;
        int fast = GetNext(n);
        while (fast != 1 && slow != fast)
        {
            slow = GetNext(slow);
            fast = GetNext(GetNext(fast));
        }
        return fast == 1;
    }

    private int GetNext(int num)
    {
        int result = 0;
        while (num > 0)
        {
            result += (num % 10) * (num % 10);
            num /= 10;
        }
        return result;
    }
}
// @lc code=end


3. 补充知识と杂谈,总结

复习一下弗洛伊德判圈法吧

  • 判圈

快慢指针,快指针每次2步,慢的每次1步,如果相遇则有圈

  • 判圈的入口在哪

在上面相遇的基础后,将快慢指针中的一个放到开头,两个指针每次走1步,相遇即圈的入口

posted @ 2026-08-08 13:43  绘星tsuki  阅读(7)  评论(0)    收藏  举报