这里有一些代码可以做到这一点。代码中的注释。基本思想是您一次遍历最后计数的数字的数字,并且对于每个数字位置,您可以计算在该位置之前具有相同数字但在该当前位置具有较小数字的数字。这些函数相互依赖,因此最后的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 时,除了数字中某处的零之外,还允许前导零序列。这增加了纯排列的计数。