leetcode#60 排列序列
题目:
给出两个数n,k,求1~n的第k个全排列(字典序)。
思路:逆康托展开
康托展开
给个排列问是第几个。
先给出结论
:康托展开的结果为ans=A[0] * (n-1)!+A[1] * (n-2)!+A[2]*(n-3)!+…。==
其中A[i]代表 位于位置i后面的数小于A[i]值的个数,然后乘上后面的位数的阶乘。
不难理解,设序列p=abcdefg,那么对任意比p小的数列,一定存在前i位与p相同第i位比i小,后i位随意排列。因此,对于任意i,满足的条件数就是后面比i小的数的个数和,后面位数的阶乘。这是对于第i位来说,小的数的个数,如果第i位不变,那么就要继续看后面的位,所以是累加和的关系。
逆康托展开
也就是这个题了。
看康托展开的结论可以看出,每一项的(n-i)!都比后面所有项的总和还大。于是可以用类似进制转换的方法,不断地模、除,来得到A的每—项。
前面已经说到康拖展开是从序列到自然数的映射且是可逆的,那么逆康拖展开便是从自然数到序列的映射列 :
在(1,2,3,4,5) 给出61可以算出起排列组合为34152
具体过程如下:
用 61 / 4! = 2余13,说明 ,说明比首位小的数有2个,所以首位为3。
用 13 / 3! = 2余1,说明 ,说明在第二位之后小于第二位的数有2个,所以第二位为4。
用 1 / 2! = 0余1,说明 ,说明在第三位之后没有小于第三位的数,所以第三位为1。
用 1 / 1! = 1余0,说明 ,说明在第二位之后小于第四位的数有1个,所以第四位为5。
细节:除完后,不能直接赋值商加1,因为,设商为2,说明比这位小的有两个,并不一定是3,因为可能有的数已经被用过了,所以,要取剩下的第三个数。
代码:
class Solution
{
public:
int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
string getPermutation(int n, int k)
{
k--;
string ans;
vector<int> ve;
for (int i = 1; i <= n; ++i)
ve.push_back(i);
for (int i = 1; i <= n; ++i)
{
int x = k / fact[n - i];
k %= fact[n - i];
ans.push_back(ve[x]+'0');
ve.erase(ve.begin() + x);
}
return ans;
}
};