康托展开
康托展开是一个全排列到一个自然数的双射,常用于构建hash表时的空间压缩。设有n个数(1,2,3,4,…,n),可以有组成不同(n!种)的排列组合,康托展开表示的就是在n个不同元素的全排列中, 比当前排列组合小的个数,那么也可以表示当前排列组合在n个不同元素的全排列中的名次(当前的名次 = 比当前排列组合小的个数 + 1)。
在(1,2,3,4,5)5个数的排列组合中,计算 34152的康托展开值。
根据公式:
X = 2 * 4! + 2 * 3! + 0 * 2! + 1 * 1! + 0 * 0! = 2 * 24 + 2 * 6 + 1 = 61
所以比 34152 小的组合有61个,即34152是第62个排列数。
def getPermutation(n:int,num:list[int]) -> int :
fac = [1]
for i in range(1, n + 1):
fac.append(fac[-1] * i)
ans = 0
for i in range(n):
samller = 0
for j in range(i + 1,n):
if num[j] < num[i]:
samller += 1
ans += fac[n - i - 1] * samller
return ans + 1
tips: 这里主要为了讲解康托展开的思路,实现的算法复杂度为O(n^2),实际当n很大时,内层循环计算在当前位之后小于当前位的个数可以用 线段树来处理计算,而不用每次都遍历,这样复杂度可以降为O(nlogn)。
逆康托展开
即对于上述例子,在(1,2,3,4,5)给出61可以算出起排列组合为 34152。
因为34152是第62个排列数,减一才是真正的逆康托展开
## 逆康托展开
def getPermutation(self, n: int, k: int) -> str:
fac = [1]
for i in range(1,n + 1):
fac.append(fac[-1] * i)
num = [i for i in range(1,n + 1)]
ans = list()
k -= 1
for i in range(n,0,-1):
pre = k % fac[i - 1]
x = k // fac[i - 1]
k = pre
ans.append(str(num[x]))
num.pop(x)
return "".join(ans)