【问题标题】:No of numbers less than a given number with no repeating digits小于给定数字且没有重复数字的数字数量
【发布时间】:2012-10-26 03:00:10
【问题描述】:

我们如何找到小于给定数字且其中没有重复数字的数字的数量?

例如,小于 100 的数字为 90。(11、22、33、44、55、66、77、88、99 有重复数字,因此被排除在外)。

同样对于小于 1000 的数字,必须排除 101、110、122、202 等数字。

【问题讨论】:

  • 您是要数这些数字还是要枚举它们?
  • 你能添加一些上下文吗?数字的最大大小是多少?速度要求是什么?为什么不简单地迭代和计数?
  • 数字的最大大小为 10^18。迭代会很慢。
  • 最大值只有 10^10,因为没有大于 9876543210 的值。你负担得起。
  • 没有代表数字的数字的数量是有限的,所以我们可以建立一个查找表来得到 O(1) 中的结果:p

标签: algorithm math combinations digits


【解决方案1】:

这是一种加快速度的方法。请注意,最大数字中的位数与解决方案(我将称之为 NON 的数字的数量)之间存在相关性

100 (3 digits) => NON = 10 * 9  
1000 (4 digits) => NON = 10 * 9 * 8  
10000 (5 digits) => NON = 10 * 9 * 8 * 7  
...  
10000000000 (11 digits) => NON = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1

十亿之后你一定会重复一个数字

【讨论】:

    【解决方案2】:

    你可以考虑两种情况:

    • 数字小于限制
    • 在某个数字处与限制不同的数字

    d-digit 的计数为9*9*8*... = 9*9!/(9-d)!(第一位可能不为零)。所有小于 d 的数字的计数是 0 位数的计数 + .. d-1 位数的计数。这些总和可能是预先计算的(甚至是硬编码的)。

    d-digit 数字和f 给出的第一个数字是(10-f)*...*(10-(d-1)) = (10-f)!/(10-d)!。您也可以预先计算阶乘。

    伪代码:

    To precompute fac:
      - fac = int[10];
      - fac[0] = 1;
      - for i in 1..10:
        - fac[i] = fac[i-1] * i;
    
    To precompute count_shorter:
      - cs = int[10];
      - cs[0] = 0;
      - cs[1] = 1; // if zero is allowed
      - for i in 1..10:
        - cs[i+1] = cs[i] + 9 * fac[9] / fac[10-i]
      - count_shorter = cs;
    
    To determine the count of numbers smaller than d:
      - sl = strlen(d)
      - if sl > 10
        - return count_shorter[11]
      - else
        - sum = 0
        account for shorter numbers:
        - sum += count_shorter[sl]
        account for same-length numbers; len=count of digits shared with the limit:
        - sum += 9* fac[9] / fac[10-sl];
        - for every len in 1..{sl-1}:
          count the unused digits less than d[len]; credits to @MvG for noting:
          - first_opts = d[len]-1;
          - for every i in 0..{len-1}:
            - if d[i] < d[len]
              - first_opts -= 1;
          - sum += first_opts * fac[9-len] / fac[10-sl] 
      - return sum
    

    【讨论】:

    • 比较你的伪代码和my code,我发现很多相似之处。您也将位置len 处的数字拆分为您保留的头部和您计算较小数字的尾部。但是代码中的 (d[len]-1) 术语忽略了这样一个事实,即已经出现在头部的数字不能再次出现在尾部。将其与我的代码中的 firstHere 计算进行比较,看看有什么不同。
    • @MvG 好点。 (d[len]-1) 术语不正确。我会更新我的答案。
    【解决方案3】:

    这里有一些代码可以做到这一点。代码中的注释。基本思想是您一次遍历最后计数的数字的数字,并且对于每个数字位置,您可以计算在该位置之前具有相同数字但在该当前位置具有较小数字的数字。这些函数相互依赖,因此最后的cntSmaller 函数是您实际调用的函数,也是具有最详细的 cmets 的函数。我已经检查了这是否与最多 30000 的所有参数的蛮力实现一致。我已经与替代实现进行了广泛的比较,所以我相当有信心这段代码是正确的。

    from math import factorial
    
    def take(n, r):
        """Count ways to choose r elements from a set of n without
        duplicates, taking order into account"""
        return factorial(n)/factorial(n - r)
    
    def forLength(length, numDigits, numFirst):
        """Count ways to form numbers with length non-repeating digits
        that take their digits from a set of numDigits possible digits,
        with numFirst of these as possible choices for the first digit."""
        return numFirst * take(numDigits - 1, length - 1)
    
    def noRepeated(digits, i):
        """Given a string of digits, recursively compute the digits for a
        number which is no larger than the input and has no repeated
        digits. Recursion starts at i=0."""
        if i == len(digits):
            return True
        while digits[i] in digits[:i] or not noRepeated(digits, i + 1):
            digits[i] -= 1
            for j in range(i + 1, len(digits)):
                digits[j] = 9
            if digits[i] < 0:
                digits[i] = 9
                return False
        return True
    
    def lastCounted(n):
        """Compute the digits of the last number that is smaller than n
        and has no repeated digits."""
        digits = [int(i) for i in str(n - 1)]
        while not noRepeated(digits, 0):
            digits = [9]*(len(digits) - 1)
        while digits[0] == 0:
            digits = digits[1:]
        assert len(digits) == len(set(digits))
        return digits
    
    def cntSmaller(n):
        if n < 2:
            return 0
        digits = lastCounted(n)
        cnt = 1 # the one from lastCounted is guaranteed to get counted
        l = len(digits)
        for i in range(1, l):
            # count all numbers with less digits
            # first digit non-zero, rest all other digits
            cnt += forLength(i, 10, 9)
        firstDigits = set(range(10))
        for i, d in enumerate(digits):
            # count numbers which are equal to lastCounted up to position
            # i but have a smaller digit at position i
            firstHere = firstDigits & set(range(d)) # smaller but not duplicate
            if i == 0: # this is the first digit
                firstHere.discard(0) # must not start with a zero
            cnt += forLength(l - i, 10 - i, len(firstHere))
            firstDigits.discard(d)
        return cnt
    

    编辑: cntSmaller(9876543211) 返回 8877690,这是您可以用非重复数字组成的最大数字数。这超过 10!=3628800 的事实让我困惑了一段时间,但这是正确的:当您考虑将序列填充到长度 10 时,除了数字中某处的零之外,还允许前导零序列。这增加了纯排列的计数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-22
      • 1970-01-01
      • 1970-01-01
      • 2021-11-01
      • 2021-11-16
      • 2019-02-04
      • 1970-01-01
      相关资源
      最近更新 更多