「LeetCode」202.快乐数

182 阅读4分钟

「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」。

题目描述🌍

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

快乐数」 定义为:

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

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

示例 1

输入:n = 19
输出:true

解释

12+92=8282+22=6862+82=10012+02+02=11^2 + 9^2 = 82 \\ 8^2 + 2^2 = 68 \\ 6^2 + 8^2 = 100 \\ 1^2 + 0^2 + 0^2 = 1

示例 2

输入:n = 2
输出:false

提示

  • 11 <= n <= 23112^{31} - 1

哈希法🚀

解题思路

⭐在开始做题之前,先分析下最终可能出现的几种情况:

  • 最终结果为 1
  • 最终陷入无限循环且到达不了 1
  • 最终数值趋于无穷大

结果为 1:示例 1 给出的就是这种情况。

无限循环且不为 1

趋于无穷大

题目限制条件中提到:n2311=2147483647n \le 2^{31}-1 = 2147483647

nextOne 是当前 n 的下一个数字

  • n = 9,nextOne = 81
  • n = 99,nextOne = 162
  • n = 999,nextOne = 243
  • n = 9999,nextOne = 324
  • ...
  • n = 9999999999,nextOne = 810

⭐对于任意一个 n 来说,它的位数就决定了其 nextOne 存在上限;而从 n=999n=999 这种情况开始,其 nextOne 的位数一定是 \le n 的位数,所以无论 n 多大,经过不断的下一个数字,其值一定会陷入不大于 243 的循环之中(该分析对于第三种解题方法至关重要)!

比如对于 3 位数的数字而言,其循环过程中的天花板数字只能是 243\le243,所以这种趋于无穷大的情况不可能出现,也就排除该情况了。

进入正题:既然结果只能是 1 或者无限循环,那我们可以用哈希表存储每一个出现过的数字,哈希表一旦重复则表明进入了循环。

代码

Java

class Solution {
    // 哈希法
    public boolean isHappy(int n) {
        Set<Integer> hashSet = new HashSet<>();
        while (!hashSet.contains(n)) {
            hashSet.add(n);
            n = nextOne(n);
            if (n == 1)
                return true;
        }
        return false;
    }

    // 获取下一个数
    private int nextOne(int n) {
        int sum = 0;
        while (n != 0) {
            int x = n % 10;
            sum += x * x;
            n /= 10;
        }
        return sum;
    }
}

C++

class Solution {
public:
    bool isHappy(int n) {
        unordered_set<int> numbers;
        while (numbers.find(n) == numbers.end()) {
            numbers.insert(n);
            n = nextOne(n);
            if (n == 1)
                return true;
        }
        return false;
    }

    int nextOne(int num) {
        int sum = 0;
        while (num != 0) {
            int x = num % 10;
            sum += x * x;
            num /= 10;
        }
        return sum;
    }
};

时间 / 空间复杂度的分析,下同

时间复杂度:O(logn)O(logn)

空间复杂度:O(logn)O(logn),与时间复杂度相关

快慢指针🥇

解题思路

我们还可以通过快慢指针来标记元素,一旦进入了循环,快指针迟早能多绕一圈然后找到慢指针,所以快慢指针一旦相同且不为 1,那么则表示陷入无限循环了。

如果不会进入循环,快指针也一定会比慢指针先找到 1;虽然快指针是前进 2 次,但不用担心会错过 1,因为 12=11^2=1

代码

Java

class Solution {
    // 快慢指针
    public boolean isHappy(int n) {
        int slow = n;
        int fast = n;
        do {
            // slow 前进2次
            slow = nextOne(slow);
            // fast 前进1次
            fast = nextOne(fast);
            fast = nextOne(fast);
            // 1^2 = 1; 所以无需担心错过1
            if (fast == 1)
                return true;
        } while (slow != fast);
        // slow == fast && fast != 1
        return false;
    }
    
    // 获取下一个数
    private int nextOne(int n) {
        int sum = 0;
        while (n != 0) {
            int x = n % 10;
            sum += x * x;
            n /= 10;
        }
        return sum;
    }
}

C++

class Solution {
public:
    bool isHappy(int n) {
        int slow = n;
        int fast = n;
        do {
            slow = nextOne(slow);
            fast = nextOne(fast);
            fast = nextOne(fast);
            if (fast == 1)
                return true;
        } while (slow != fast);
        return false;
    }

    int nextOne(int num) {
        int sum = 0;
        while (num != 0) {
            int x = num % 10;
            sum += x * x;
            num /= 10;
        }
        return sum;
    }
};

时间复杂度:O(logn)O(logn)

空间复杂度:O(1)O(1)

数学法🛕

解题思路

在分析为何最终值不会趋于无穷大的那部分,我们得出了一个结论:如果存在循环,那么一定会陷入不大于 243 的循环之中

通过仔细查找就可以锁定唯一的循环了:416375889145422044-16-37-58-89-145-42-20-4,所以一旦出现循环,无论如何都逃脱不了这个唯一的循环圈。

代码

Java

class Solution {
    // 数学方法
    public boolean isHappy(int n) {
        // 保存常数个数字
        Set<Integer> hashSet = new HashSet<>(Arrays.asList(4, 16, 37, 58, 89, 145, 42, 20));
        while (n != 1 && !hashSet.contains(n)) {
            n = nextOne(n);
        }
        return n == 1;
    }
    
    private int nextOne(int n) {
        int sum = 0;
        while (n != 0) {
            int x = n % 10;
            sum += x * x;
            n /= 10;
        }
        return sum;
    }
}

C++

class Solution {
public:
    bool isHappy(int n) {
        unordered_set<int> circulation{4, 16, 37, 58, 89, 145, 42, 20};
        while (n != 1 && circulation.find(n) == circulation.end()) {
            n = nextOne(n);
        }
        return n == 1;
    }

    int nextOne(int num) {
        int sum = 0;
        while (num != 0) {
            int x = num % 10;
            sum += x * x;
            num /= 10;
        }
        return sum;
    }
};

时间复杂度:O(logn)O(logn)

空间复杂度:O(1)O(1),因为仅保存常数个值到哈希表中。

最后🌅

该篇文章为 「LeetCode」 系列的 No.25 篇,在这个系列文章中:

  • 尽量给出多种解题思路
  • 提供题解的多语言代码实现
  • 记录该题涉及的知识点

👨‍💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!