第k个字典序全排列(kth permutation)- 康托排列

给定一个数字n和k,表示求一个由n个数字组成的序列的第k个字典序排列。比如给定n=3,k = 2则求由数字【1,2,3】组成的所有序列中的安字典序排列的第k个。不难看出这三个数字组成的序列的按字典序排序如下:
1,2,3
1,3,2
2,1,3
2,3,1
3,1,2
3,2,1
所以得到的第k=2个序列就是1,3,2。

分析
  1. 可以用上一篇文章所说的NextPermutation方法在初始序列为【1,2,3,···n】的基础上调用k-1次nextPermutation方法即可。
  2. 可以利用康托编码来实现。
康托编码:

    对于一个集合{1,2,3,4,...,n}的从小到大排序(或者从大到小,与从小到大类似,这里只说前者)的全排列显然它有n!项用自然数1,2,...,n!与之一一对应,这是一种对应法则,康托编码就是这么一种可以实现按字典序排序的序列与自然数之间相互对应的双射。

编码规则为:

对于{1,2,3,...,n}生成的已经从小到大排序好的全排列
x=a[n]*(n-1)!+a[n-1]*(n-2)!+...a[1]*0!
a[m]代表比在第m位的数字小并且没有在第m位之前出现过的数字的个数(以个位数为第1位)
x代表比这个数小的数的个数,所以这个数的顺序就是x+1


例1 {1,2,3,4,5}的全排列,并且已经从小到大排序完毕,找出45231在这个排列中的顺序

比4小的数有3个
比5小的数有4个,但4已经在之前出现过了所以是3个
比2小的数有1个
比3小的数有两个,但2已经在之前出现过了所以是1个
比1小的数有0个
那么45231在这个排列中的顺序是3*4!+3*3!+1*2!+1*1!+0*0!+1=94


例2 {1,2,3,4,5,6}的全排列,并且已经从小到大排序完毕,找出423615在这个排序中的顺序
比4小的数有3个
比2小的数有1个
比3小的数有2个,但2已经在之前出现过了所以是1个
比6小的数有5个,但4,2,3已经在之前出现过了所以是2个
比1小的数有0个
比5小的数有4个,但1,2,3,4已经在之前出现过了所以是0个
那么423615在这个排序中的顺序是3*5!+1*4!+1*3!+2*2!+0*1!+0*0!+1=395


解码规则为:

解码规则是编码规则的逆运算,即在知道某个序列的排列序数(y)的时候,求这个序列。先对y-1,得到x,然后分别求序列的每个位置的数。举例说明

例1 {1,2,3,4,5}的全排列,并且已经从小到大排序完毕
(1)找出第96个数
首先用96-1得到95
用95去除4! 得到3余23
用23去除3! 得到3余5
用5去除2!得到2余1
用1去除1!得到1余0
有3个数比它小的数是4,所以第一位是4
有3个数比它小的数是4,但4已经在之前出现过了所以是5(因为4在之前出现过了所以实际比5小的数是3个)
有2个数比它小的数是3
有1个数比它小的数是2
最后一个数只能是1
所以这个数是45321


(2)找出第16个数
首先用16-1得到15
用15去除4!得到0余15
用15去除3!得到2余3
用3去除2!得到1余1
用1去除1!得到1余0
有0个数比它小的数是1
有2个数比它小的数是3, 但由于1已经在之前出现过了所以是4(因为1在之前出现过了所以实际比4小的数是2)
有1个数比它小的数是2 ,但由于1已经在之前出现过了所以是3(因为1在之前出现过了所以实际比3小的数是1)
有1个数比它小得数是2 ,但由于1,3,4已经在之前出现过了所以是5(因为1,3,4在之前出现过了所以实际比5小的数是1)
最后一个数只能是2
所以这个数是14352

class Solution {
/*
method 1:
用next_permutation来求,就是求其下一个排列,然后对1234…n 运行n次 next_pernutation方法就能得到第k个数
*/
public:

/*
method 2:
利用康托编码来求。
其实就是康托展开的逆过程。
康托展开用来求某个全排列数是第几小的数,也就是当这些数按顺序排时第几个数。
过程如下:比如求321 是 第几小的,可以这样来想:小于3的数有1和2 两个,首位确定之后后面两位有2!中情况,所以共有2*2!=4种。
小于2的数只有一个1,所以有1*1!=1种情况,最后一位是1,没有比一小的数,所以是0*0!=0
综上:小于321的数有4+1=5个,所以321是第六小的数。

逆过程就是已知这个数是第k个数,求这个数是多少,当然是知道n的值的。
第k个数就是有k-1个数比这个数小。
所以就是 k-1=an*(n-1)!+an-1*(n-2)!+....+a1*0!;
*/
    /*
     * @param n: n
     * @param k: the k th permutation
     * @return: return the k-th permutation
     */
    string getPermutation(int n, int k) {
        // write your code here
        string s(n, '0');
        for (int i = 0; i < n; ++i) // Initialize
            s[i] += i + 1;
            
        return kth_permutation(s, k);
    }
private:
    int factorial(int n) {
        int result = 1;
        for (int i = 1; i <= n; ++i)
            result *= i;
        return result;
    }
    // seq 已排好序,是第一个排列
    template<typename Sequence>
    Sequence kth_permutation(const Sequence &seq, int k) {
        const int n = seq.size();
        Sequence S(seq);
        Sequence result;
        int base = factorial(n - 1);
        --k; // 康托编码从0 开始
        for (int i = n - 1; i > 0; k %= base, base /= i, --i) {
            auto a = next(S.begin(), k / base);
            result.push_back(*a);
            S.erase(a);
        }
        result.push_back(S[0]); // 最后一个
        return result;
    }
};


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值