哈希表题目:快乐数

题目

标题和出处

标题:快乐数

出处: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;
    }
}

复杂度分析

  • 时间复杂度: 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;
    }
}

复杂度分析

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值