首先,我们可以将问题简化并递归地思考来轻松解决它。
假设输入序列中的所有元素都是唯一的,那么“独特”的排列集就是排列集本身。
现在要找到序列 a_1, a_2, a_3, ..., a_n
在其排列集中的秩,我们可以:
对序列进行排序,得到 b_1, b_2, ..., b_n
。这个排列的秩根据定义为 0
。
现在比较 a_1
和 b_1
。如果它们相同,那么我们可以直接从问题中删除它们: a_1, a_2, ..., a_n
的秩与只有 a_2, ..., a_n
的秩相同。
否则,b_1 < a_1
,但是随后以 b_1
开始的所有排列都会小于 a_1, a_2, ..., a_n
。这样的排列数很容易计算,只需 (n-1)! = (n-1)*(n-2)*(n-3)*...*1
。
然后我们可以继续查看序列 b_1, ..., b_n
。如果 b_2 < a_1
,那么以 b_2
开始的所有排列也将更小。
因此,我们应该再次将 (n-1)!
添加到我们的秩中。
我们一直这样做,直到找到一个索引 j
,其中 b_j == a_j
,然后进入第2步。
这可以很容易地实现:
import math
def permutation_rank(seq):
ref = sorted(seq)
if ref == seq:
return 0
else:
rank = 0
f = math.factorial(len(seq)-1)
for x in ref:
if x < seq[0]:
rank += f
else:
rank += permutation_rank(seq[1:]) if seq[1:] else 0
return rank
这个解决方案非常快速:
In [24]: import string
...: import random
...: seq = list(string.ascii_lowercase)
...: random.shuffle(seq)
...: print(*seq)
...: print(permutation_rank(seq))
...:
r q n c d w s k a z b e m g u f i o l t j x p h y v
273956214557578232851005079
关于相同元素的问题:它们发挥作用的关键在于,考虑每个元素与其他元素不同的排列数为(n-1)!。如果您有一个长度为n的序列,由符号s_1,...,s_k和符号s_j组成,出现c_j次,则唯一排列的数量为(n-1)! / (c_1! * c_2! * ... * c_k!)。
这意味着我们不仅需要加上(n-1)!,还要将其除以该数字,并且我们希望减少当前正在考虑的符号的计数c_t。
可以通过以下方式完成:
import math
from collections import Counter
from functools import reduce
from operator import mul
def permutation_rank(seq):
ref = sorted(seq)
counts = Counter(ref)
if ref == seq:
return 0
else:
rank = 0
f = math.factorial(len(seq)-1)
for x in sorted(set(ref)):
if x < seq[0]:
counts_copy = counts.copy()
counts_copy[x] -= 1
rank += f//(reduce(mul, (math.factorial(c) for c in counts_copy.values()), 1))
else:
rank += permutation_rank(seq[1:]) if seq[1:] else 0
return rank
我相信有一种方法可以避免复制计数字典,但现在我很累,所以我会让读者自己练习。
供参考,最终结果:
In [44]: for i,x in enumerate(sorted(set(it.permutations('aabc')))):
...: print(i, x, permutation_rank(x))
...:
0 ('a', 'a', 'b', 'c') 0
1 ('a', 'a', 'c', 'b') 1
2 ('a', 'b', 'a', 'c') 2
3 ('a', 'b', 'c', 'a') 3
4 ('a', 'c', 'a', 'b') 4
5 ('a', 'c', 'b', 'a') 5
6 ('b', 'a', 'a', 'c') 6
7 ('b', 'a', 'c', 'a') 7
8 ('b', 'c', 'a', 'a') 8
9 ('c', 'a', 'a', 'b') 9
10 ('c', 'a', 'b', 'a') 10
11 ('c', 'b', 'a', 'a') 11
并且展示它是高效的:
In [45]: permutation_rank('zuibibzboofpaoibpaybfyab')
Out[45]: 246218968687554178
find_all
而不是rank
/permutation_rank
之类的名称呢? - Bakuriu