Here is the 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 each digit position you can count the numbers that have the same digits to this position, but a smaller digit in this current position. Functions are built on top of each other, so the cntSmaller function at the very end is the one you actually call, as well as the one that contains the most detailed comments. I checked that this is consistent with brute force implementation for all arguments up to 30,000. I made extensive comparisons with alternative implementations, so I'm sure 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
Edit: cntSmaller(9876543211) returns 8877690, which is the maximum number of numbers that you can generate using non-repeating digits. The fact that this is more than 10! = 3628800 confused me a little, but itβs right: if you think that your sequences are filled with a length of 10, then leading zeros are allowed instead of zeros somewhere in the number. This increases the counter above the number of net lookups.
source share