小C的好数

191 阅读5分钟

问题描述

小C对“好数”非常感兴趣,她定义一个不含前导零的正整数为“好数”,如果它的所有数位最多包含两种不同的数字。例如,数字 23,2323,9,111,和 101 都是好数。现在小C想知道,从1到 nnn 之间有多少个好数。

例如:当 n=110n = 110n=110 时,所有的1位数、2位数,以及一些3位数(如 100, 101)都是好数,一共有 102 个。

测试样例

  • 样例 1:

    • 输入:n=110n = 110n=110
    • 输出:102
  • 样例 2:

    • 输入:n=1000n = 1000n=1000
    • 输出:352
  • 样例 3:

    • 输入:n=1n = 1n=1
    • 输出:1

思路分析

  1. 定义“好数”

    • 一个“好数”是一个不含前导零的正整数,且其所有数位最多包含两种不同的数字。举例来说,数字 9、101、23 都是“好数”,而数字 123、234 等则不是,因为它们含有超过两种不同的数字。
  2. 问题转化

    • 问题的核心是计算从 1 到 nnn 之间有多少个数字是“好数”。
    • 我们可以遍历所有从 1 到 nnn 的数字,然后对于每个数字检查它是否符合“好数”的定义。如果符合条件,则计数器增加。
  3. 判断数字是否为“好数”

    • 将数字转化为字符串,遍历每个数字字符,使用集合 HashSet 来存储这些字符。由于 HashSet 会自动去重,因此我们只需要判断 HashSet 中不同元素的数量。如果元素数量不超过 2,则该数字为“好数”。
  4. 算法步骤

    • 遍历从 1 到 nnn 的每个数字。
    • 对于每个数字,检查其是否是“好数”。
    • 如果是“好数”,计数器增加 1。
    • 最后返回计数器的值,即从 1 到 nnn 之间的“好数”数量。

Java代码实现

import java.util.HashSet;

public class Main {
    
    // 主方法:计算从 1 到 n 之间的好数数量
    public static int solution(int n) {
        int count = 0;  // 初始化计数器,用来记录好数的数量

        // 遍历从 1 到 n 的所有数字
        for (int i = 1; i <= n; i++) {
            if (isGoodNumber(i)) {  // 如果该数字是好数,计数器加 1
                count++;
            }
        }

        return count;  // 返回好数的总数
    }

    // 辅助方法:判断一个数字是否是好数
    private static boolean isGoodNumber(int number) {
        String numStr = String.valueOf(number);  // 将数字转为字符串
        HashSet<Character> digits = new HashSet<>();  // 用 HashSet 存储数字的不同字符

        // 遍历数字字符串中的每个字符
        for (char c : numStr.toCharArray()) {
            digits.add(c);  // 将字符添加到 HashSet 中
            if (digits.size() > 2) {  // 如果不同的数字种类超过 2,返回 false
                return false;
            }
        }

        return true;  // 如果数字中不超过 2 种不同的数字,返回 true
    }

    public static void main(String[] args) {
        // 进行一些基本的测试,检查结果是否正确
        System.out.println(solution(110) == 102);  // 输出 true,表示 solution(110) 的结果是正确的
        System.out.println(solution(1000) == 352); // 输出 true,表示 solution(1000) 的结果是正确的
        System.out.println(solution(1) == 1);      // 输出 true,表示 solution(1) 的结果是正确的
    }
}

代码分析

  1. solution(int n) 方法

    • 该方法是问题的核心部分,负责计算从 1 到 nnn 之间的好数数量。
    • 它遍历从 1 到 nnn 的每个数字,并调用 isGoodNumber(i) 来检查该数字是否为好数。如果是好数,增加计数。
    • 最后返回计数器 count,即符合条件的好数数量。
  2. isGoodNumber(int number) 方法

    • 这个方法检查给定的数字是否为好数。通过将数字转换为字符串,然后遍历字符串的每个字符,将字符存入 HashSet 中。
    • 如果 HashSet 中的元素个数超过 2,则说明该数字包含超过两种不同的数字,返回 false。否则,返回 true,说明该数字是一个好数。
  3. main(String[] args) 方法

    • main 方法中,我们进行了几组测试:

      • solution(110) 应该输出 102。
      • solution(1000) 应该输出 352。
      • solution(1) 应该输出 1。
    • 每个测试结果与预期结果进行比对,验证代码的正确性。


时间复杂度分析

  • 外层循环:我们遍历从 1 到 nnn 的每个数字,时间复杂度为 O(n)O(n)O(n)。
  • 内层循环:对于每个数字,我们将其转换为字符串并遍历每个字符,时间复杂度是 O(d)O(d)O(d),其中 ddd 是数字的位数。通常情况下,数字的位数 ddd 是对数级别的,即 d=log⁡10(n)d = \log_{10}(n)d=log10​(n)。
  • 因此,整个程序的时间复杂度是 O(n⋅d)O(n \cdot d)O(n⋅d),其中 ddd 通常是 O(log⁡10(n))O(\log_{10}(n))O(log10​(n)),所以总体复杂度接近 O(n)O(n)O(n)。

总结

  • 该代码通过简单的遍历和判断每个数字是否为“好数”来解决问题。对于每个数字,我们通过 HashSet 来统计其中不同的数字种类,若种类不超过 2,则认为它是好数。
  • 该算法的时间复杂度为 O(n⋅log⁡(n))O(n \cdot \log(n))O(n⋅log(n)),在 nnn 较小的情况下表现良好,适用于中等规模的输入。