给出集合[1,2,3,...,n],其所有元素共有n!种排列。
按大小顺序列出所有排列情况,并一一标记,当n = 3时,所有排列如下:
"123""132""213""231""312""321"
给定n和k,返回第k个排列。
提示:
1 <= n <= 91 <= k <= n!
示例:
输入: n = 3, k = 3
输出: "213"
题解:
/**
* @description: 逆康托展开 TC:O(n) SC:O(n)
* @author: JunLiangWang
* @param {*} n 生成 n!种排列
* @param {*} k 返回第 k 个排列
* @return {*}
*/
function inverseCantorExpansion(n, k) {
/**
* 本方案使用逆康托展开的方法,试想当n=3时,产生如下序列:
*
* 1."123"
* 2."132"
* 3."213"
* 4."231"
* 5."312"
* 6."321"
*
* 我们可以观察发现生成的序列的第一列为1,1,2,2,3,3
* 从1到3两个一组,开始我们剩余未选择的字符为1,2,3,因此
* 我们可以通过(k-1)/2得到未选择的字符的索引,假设k=1或2,此
* 时计算出索引为0,第一个字符得出为1,剩余未选择的字符为2,3;
* 假设k=3或4,此时计算出索引为1,第一个字符得出为2,剩余未选择
* 的字符为1,3。上述公式中的2我们不难发现其实是n-1的阶乘,如果我们
* 一开始就把k减去1,此处公式为k/(n-1的阶乘),设其结果为R1
*
* 第二个字符呢?此时我们发现第二个字符的可以通过k%(n-1的阶乘)/(n-2的阶乘)
* 得到未选择的字符的索引,比如k=1,计算得出索引为0,则得到未选择字符中的2,
* k=3,计算得出索引为1,则得到未选择字符中的1。
*
* 第三个字符则以此类推公式为k%(n-1的阶乘)%(n-2的阶乘)/(n-3的阶乘)
*
* 这种方式则被称为逆逆康托展开,我们定义一个字符串记录剩余未被选择的字符,
* 然后利用迭代模拟逆康托展开过程,不断选择/删除剩余未被选择的字符,即可
* 获得答案
*
*
*/
// 记录剩余未被选择的字符,初值为[1,2,3......,n]
let record = '',
// 记录当前阶乘,初值为n-1的阶乘
factorial = 1,
// 记录输出数组
outString = '';
// 遍历生成record/factorial初值
for (let i = 1; i <= n; i++) {
factorial *= i;
record += i
}
// k从1开始,需要将其减1,使之从0开始,方便选择record字符
k--;
// 迭代模拟逆康托展开过程
while (n >= 1) {
// 将当前阶乘除以n,以此不断获得n-1的阶乘,n-2的阶乘.....1的阶乘
factorial = factorial / n;
// 通过公式k/(n-i的阶乘),i从1到n-1,计算获得索引
let index = Math.floor(k / factorial);
// 从剩余未被选择的字符串中选择字符并记录
outString += record[index];
// 该字符已被选择,从剩余未被选择的字符串中删去
record = record.replace(record[index], '')
// 将k赋值为k%(n-i的阶乘),i从1到n-1,得到下一次k值
k = k % factorial;
n--;
}
// 返回结果
return outString;
}
来源:力扣(LeetCode)