• 哈希表题目:快乐数


    题目

    标题和出处

    标题:快乐数

    出处:202. 快乐数

    难度

    2 级

    题目描述

    要求

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

    「快乐数」定义为:

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

    如果 n \texttt{n} n 是快乐数就返回 true \texttt{true} true;不是,则返回 false \texttt{false} false

    示例

    示例 1:

    输入: n   =   19 \texttt{n = 19} n = 19
    输出: true \texttt{true} true
    解释:
    1 2 + 9 2 = 82 \texttt{1}^\texttt{2} + \texttt{9}^\texttt{2} = \texttt{82} 12+92=82
    8 2 + 2 2 = 68 \texttt{8}^\texttt{2} + \texttt{2}^\texttt{2} = \texttt{68} 82+22=68
    6 2 + 8 2 = 100 \texttt{6}^\texttt{2} + \texttt{8}^\texttt{2} = \texttt{100} 62+82=100
    1 2 + 0 2 + 0 2 = 1 \texttt{1}^\texttt{2} + \texttt{0}^\texttt{2} + \texttt{0}^\texttt{2} = \texttt{1} 12+02+02=1

    示例 2:

    输入: n   =   2 \texttt{n = 2} n = 2
    输出: false \texttt{false} false

    数据范围

    • 1 ≤ n ≤ 2 31 − 1 \texttt{1} \le \texttt{n} \le \texttt{2}^\texttt{31} - \texttt{1} 1n2311

    解法一

    思路和算法

    将一个正整数替换为它每个位置上的数字的平方和,称为一次操作。

    根据快乐数的定义,如果从正整数 n n n 开始,经过若干次操作(可以是零次)之后变为 1 1 1,则 n n n 是快乐数。

    • 如果正整数 n n n 是快乐数,则当 n n n 变为 1 1 1 之后,每次操作将停留在 1 1 1

    • 如果正整数 n n n 不是快乐数,则每次操作得到的数形成无限循环,该无限循环不包含 1 1 1,一定存在某一次操作,该次操作之后变为的数是已经出现过的数。

    是否可能每次操作得到的数趋向于无穷大?答案是否定的,理由如下。

    1. 由于正整数 n n n 的最大值是 2 31 − 1 2^{31} - 1 2311 n n n 最多是 10 10 10 位数,因此对 n n n 进行一次操作之后得到的最大可能数是 9 2 × 10 = 810 9^2 \times 10 = 810 92×10=810(其实 810 810 810 也是不可能达到的,因为此时 n = 1 0 10 − 1 > 2 31 − 1 n = 10^{10} - 1 > 2^{31} - 1 n=10101>2311,为了简化逻辑假设最大可能数是 810 810 810),最大可能数是三位数。

    2. 由于最大的三位数是 999 999 999(同上,其实 999 999 999 也是不可能达到的,为了简化逻辑故考虑最大的三位数),对任意三位数进行一次操作之后得到的最大可能数是 9 2 × 3 = 243 9^2 \times 3 = 243 92×3=243

    3. 对任意不超过 243 243 243 的正整数进行一次操作之后得到的最大可能数是 163 163 163(当正整数是 199 199 199 时可得到 163 163 163), 163 < 243 163 < 243 163<243

    根据上述分析可知,对于任意不超过 2 31 − 1 2^{31} - 1 2311 的正整数,经过两次操作之后得到的数一定不超过 243 243 243,之后经过任意次操作得到的数也一定不超过 243 243 243,因此每次操作得到的数不可能趋向于无穷大,而是一定会出现循环。

    实现方面,需要考虑两个部分。

    1. 对于一个数进行一次操作,可以将数转换成字符串之后遍历字符串的每一位计算平方和,也可以直接对给定的数做数位分离计算每一位的平方和,这里使用的是数位分离的做法。

    2. 判断每次操作之后变为的数是否已经出现过,需要使用哈希集合存储每次操作之后的数。

    由此可以得到判断正整数 n n n 是不是快乐数的方法。

    1. 从正整数 n n n 开始,每次将 n n n 加入哈希集合,然后对 n n n 进行一次操作并用操作后的数替换 n n n,直到 n n n 已经在哈希集合中,此时出现循环。

    2. 当出现循环时,如果 n = 1 n = 1 n=1 则返回 true \text{true} true,否则返回 false \text{false} false

    代码

    class Solution {
        public boolean isHappy(int n) {
            Set<Integer> set = new HashSet<Integer>();
            while (set.add(n)) {
                n = getNextNumber(n);
            }
            return n == 1;
        }
    
        public int getNextNumber(int n) {
            int sum = 0;
            while (n != 0) {
                int digit = n % 10;
                n /= 10;
                sum += digit * digit;
            }
            return sum;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    复杂度分析

    • 时间复杂度: O ( log ⁡ n ) O(\log n) O(logn)。这道题的时间复杂度分析颇为困难,以下给出近似分析。
      每次操作需要遍历数的每一位,时间复杂度是 O ( log ⁡ n ) O(\log n) O(logn)
      由于对于任意 n n n 经过至多两次操作之后得到的数一定不超过 243 243 243,之后经过任意次操作得到的数也一定不超过 243 243 243,因此允许的操作次数是有限的。
      由于允许的操作次数有限,因此时间复杂度是 O ( 243 log ⁡ n ) = O ( log ⁡ n ) O(243 \log n) = O(\log n) O(243logn)=O(logn)

    • 空间复杂度 O ( log ⁡ n ) O(\log n) O(logn)。空间复杂度主要取决于哈希表,哈希表存储原始正整数 n n n 和每次操作变成的数,哈希表大小为经过的数的个数。

    解法二

    思路和算法

    根据解法一可知,无论 n n n 是不是快乐数,对 n n n 进行足够多次数的操作之后都会出现循环。如果 n n n 是快乐数,则循环将出现在 1 1 1 的位置;如果 n n n 不是快乐数,则循环将不包含 1 1 1

    对于循环问题,一个常用的方法是快慢指针。快慢指针一般用于判断是否存在循环,由于这道题中一定存在循环,因此快慢指针用于判断出现循环的数,只需要判断出现循环的数是不是 1 1 1 即可。

    从起点开始,每次将快指针移动两步,慢指针移动一步,在至少移动一次的情况下,当快慢指针相遇时停止,此时判断快慢指针所在的数字是不是 1 1 1,即可判断 n n n 是不是快乐数。

    代码

    class Solution {
        public boolean isHappy(int n) {
            int fast = n, slow = n;
            do {
                fast = getNextNumber(fast);
                fast = getNextNumber(fast);
                slow = getNextNumber(slow);
            } while (fast != slow);
            return fast == 1;
        }
    
        public int getNextNumber(int n) {
            int sum = 0;
            while (n != 0) {
                int digit = n % 10;
                n /= 10;
                sum += digit * digit;
            }
            return sum;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    复杂度分析

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

    • 空间复杂度: O ( 1 ) O(1) O(1)。只需要维护快慢指针。

  • 相关阅读:
    2024年,提升Windows开发和使用体验的实践经验 - RIME输入法
    Java---数据库---数据库约束,设计,多表,事务
    AIGC革新,将文字或者LOGO融入AI视频基于PIKA-labs(Python3.10)
    深入了解 JavaScript 中的 DOM 和 BOM
    JDBC技术
    游戏服务器价格对比分析,2024高主频高性能服务器租用价格
    Django面对高并发现象时处理方法
    1519_AURIX TC275 SRI总线部分相关寄存器的梳理
    HTML制作一个汽车介绍网站【大学生网页制作期末作业】
    【Python】创建一个简易服务器并实现移动端快速访问电脑文件
  • 原文地址:https://blog.csdn.net/stormsunshine/article/details/121452322