计算具有唯一数字的数字

2025/04/19

1. 概述

在本文中,我们将探讨计数具有唯一数字的数字的问题,使得数字的总数字位数不超过给定的整数。

我们将通过示例和时间和空间复杂度的分析来研究从暴力到优化方法的有效解决方案。

2. 问题陈述

给定一个整数n,我们需要统计所有具有唯一数字的正数x,使得:

  • 0 <= x <10n即xi是小于10n的正整数
  • x中的每个数字都是唯一的,没有重复

以下是一些示例:

  • 如果n = 0,则唯一满足我们条件的数字是0
  • 如果n = 1,则所有一位数都算数:0、1、2、3、4、5、6、7、8、9、10
  • 如果n = 2,则所有一位数都算数:0、1、2、3、4、5、6、7、8、9、10、12、13等(注意,不包括11)
  • 如果n = 3,则唯一数字小于1000的数字包括0、1、2、3、12、13、23、345、745等
  • 如果n = 4,则唯一数字小于1000的数字将包括0、1、2、3、12、13、23、345、745、1234、1567等

3. 解决方案

3.1 暴力破解方法

一种直接的方法是暴力法,即生成所有小于10n的数字,并检查每个数字是否唯一。然而,随着n的增加,这种方法的计算成本会变得越来越高。

在暴力破解方法中,我们循环遍历所有小于10n的数字。对于每个数字,验证其数字是否唯一,并计算有多少个数字满足以下条件:

int bruteForce(int n) {
    int count = 0;
    int limit = (int) Math.pow(10, n);
        
    for (int num = 0; num < limit; num++) {
        if (hasUniqueDigits(num)) {
            count++;
        }
    }
    return count;
}

hasUniqueDigits函数通过将给定数字转换为字符串并将每个数字与其他数字进行比较来检查该数字是否没有重复的数字,如果发现任何重复的数字,则返回false;否则,返回true:

boolean hasUniqueDigits(int num) {
    String str = Integer.toString(num);
    for (int i = 0; i < str.length(); i++) {
        for (int j = i + 1; j < str.length(); j++) {
            if (str.charAt(i) == str.charAt(j)) {
                return false;
            }
        }
    }
    return true;
}

让我们通过执行以下测试来验证该算法:

@Test
void givenNumber_whenUsingBruteForce_thenCountNumbersWithUniqueDigits() {
    assertEquals(91, UniqueDigitCounter.bruteForce(2));
}

时间复杂度为O(10n),其中n是最大数字的位数,因为我们需要检查每个数字的数字唯一性。由于每个数字都采用字符串表示,因此空间复杂度为O(n)

3.2 组合方法

为了优化这个过程,我们可以采用组合方法。我们不是使用暴力,而是通过分别考虑每个数字的位置并利用排列来计算有效数字的数量

这种方法比暴力破解效率高得多,它只需要对每个数字进行持续计算,因此即使n值较大,这种方法也是可行的。

让我们看看以下观察结果:

  • 当n = 0时,返回1,因为唯一有效数字是0

  • 当n = 1时,数字可以是0到9,从而产生10个可能的数字

  • 当n > 1时:

    • 第一个数字有9个可能的选择(1到9,因为0不能作为第一个数字)
    • 第二位数字有9个可能的选择(0到9,不包括第一位数字)
    • 第三位数字有8个选择,依此类推

对于k位数字,可以使用排列来计算有效数字的数量:

int combinatorial(int n) {
    if (n == 0) return 1;
    
    int result = 10;
    int current = 9;
    int available = 9;

    for (int i = 2; i <= n; i++) {
        current *= available;
        result += current;
        available--;
    }
    return result;
}

让我们测试一下上述方法是否正确运行:

@Test
void givenNumber_whenUsingCombinatorial_thenCountNumbersWithUniqueDigits() {
    assertEquals(91, UniqueDigitCounter.combinatorial(2));
}

时间复杂度为O(n),因为我们处理每个数字一次,而空间复杂度为O(1),因为我们只使用几个变量

4. 总结

在本文中,我们探讨了两种计数小于n且唯一数字个数的方法。暴力法会检查所有数字,但当n较大时效率低下;而组合法则利用排列,可以得到高效的O(n)解法。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章