数字小于给定数字且没有重复数字

时间:2012-11-06 07:22:58

标签: algorithm math combinations digits

我们怎样才能找到小于给定数字但没有重复数字的数字?

例如,小于100的这些数字的数量是90.(11,22,33,44,53,67,77,88,99具有重复数字,因此被排除在外)。

同样,如果小于1000,则必须排除101,110,122,202等数字。

3 个答案:

答案 0 :(得分:2)

这是一种让它更快的方法。请注意,最大数字和解决方案中的位数之间存在相关性(我将称之为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

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

答案 1 :(得分:2)

您可以考虑两种情况:

  • 数字短于限额
  • 与某些数字的限制不同的数字

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

d个数字,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

答案 2 :(得分:0)

这是执行此操作的一些代码。代码中的注释。基本思想是您一次迭代最后一个计数的数字的数字,并且对于每个数字位置,您可以计算在该位置之前具有相同数字但在该当前位置处具有较小数字的数字。这些函数相互构建,因此最后的cntSmaller函数是您实际调用的函数,也是具有最详细注释的函数。我已经检查过,这与高达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长度时,除了数字中的某个零之外,还允许前导零序列。这会使计数增加到高于纯排列的数量。