No of numbers less than a given number with no repeating digits

半城伤御伤魂 提交于 2019-12-19 03:42:47

问题


How can we find the number of numbers less than a given number with no repeating digits in it?

For example the number of such numbers less than 100 is 90. (11, 22, 33,44, 55,66,77,88,99 have repeating digits so are excluded).

Similarly for less than 1000, digits like 101, 110, 122, 202 etc have to be excluded.


回答1:


Here is a way to make it quicker. Notice that there is a correlation between the number of digits in the max number and the solution (number of numbers which I will call 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

after one billion you're bound to repeat a digit




回答2:


You can consider two cases:

  • numbers shorter than the limit
  • numbers that that differ from the limit at some digit

The count of d-digit numbers is 9*9*8*... = 9*9!/(9-d)! (the first digit may not be zero). The count of all numbers shorter than d is the count of 0-digit numbers + .. count of d-1-digit numbers. These sums may be precomputed (or even hard-coded).

The count of d-digit numbers with f first digits given is (10-f)*...*(10-(d-1)) = (10-f)!/(10-d)!. You can precomupte the factorials as well.

Pseudocode :

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



回答3:


Here is some code that does this. Comments in the code. The basic idea is that you iterate over the digits of the last counted number one at a time, and for every digit position you can count the numbers that have the same digits prior to that position but a smaller digit at that current position. The functions build upon one another, so the cntSmaller function at the very end is the one you'd actually call, and also the one with the most detailed comments. I've checked that this agrees with a brute-force implementation for all arguments up to 30000. I've done extensive comparisons against alternate implementations, so I'm fairly confident that this code is correct.

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

Edit: cntSmaller(9876543211) returns 8877690 which is the maximum number of numbers you can form with non-repeating digits. The fact that this is more than 10!=3628800 had me confused for a while, but this is correct: when you consider your sequences padded to length 10, then sequences of leading zeros are allowed in addition to a zero somewhere in the number. This increases the count above that of the pure permutations.



来源:https://stackoverflow.com/questions/13246275/no-of-numbers-less-than-a-given-number-with-no-repeating-digits

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!