难度:困难
给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"
给定 n 和 k,返回第 k 个排列。
输入:n = 3, k = 3
输出:"213"
输入:n = 4, k = 9
输出:"2314"
输入:n = 3, k = 1
输出:"123"
/**
* 回溯
* @desc 时间复杂度 O(N!) 空间复杂度 O(1)
* @param n {number}
* @param k {number}
* @return {string}
*/
export function getPermutation(n: number, k: number): string {
let idx = 1;
return backtrack('') || '';
function backtrack(str: string): string | undefined {
if (str.length === n && idx === k) {
return str;
} else if (str.length === n) {
idx++;
} else {
for (let i = 1; i <= n; i++) {
if (str.split('').includes(`${i}`)) continue;
const res = backtrack(str + i);
if (res) {
return res;
}
}
}
}
}对于
n个不同的元素(例如数n1,2,⋯,n),它们可以组成的排列总数目为n!。
对于给定的n和k,我们可以从左到右确定第k个排列中的每个位置上的元素到底是什
么。
我们首先确定首个元素a1,根据上面的结论,我们可以知道:
-
以
1为a1的排列一共有(n-1)!个 -
以
2为a1的排列一共有(n-1)!个 -
...
-
以
n为a1的排列一共有(n-1)!个
由于我们需要求出从小到大的第k个排列,因此:
-
如果
k≤(n−1)!,我们就可以确认排列的首个元素为1, -
如果
(n−1)!<k≤2⋅(n−1)!,我们就可以确认排列的首个元素为2, -
...
-
如果
(n−1)⋅(n−1)!<k≤n⋅(n−1)!,我们就可以确认排列的首个元素为n
因此,第k个排列的首个元素就是:a1 = ⌊(k - 1) / (n - 1)!⌋ + 1
⌊x⌋表示将x向下取整
当我们确认了a1后,可以使用相似的思路,确认下一个元素a2:
-
以
[1,n]\a1最小的元素为a2的排序一共有(n - 2)!个; -
以
[1,n]\a1次小的元素为a2的排序一共有(n - 2)!个; -
...
-
以
[1,n]\a1最大的元素为a2的排序一共有(n - 2)!个;
[1,n]\a1表示1,2,...,n中除去a1以外元素的集合
这些排序从编号(a1 - 1)(n - 1)!开始,到a1(n - 1)结束,总计(n-1)!个,因此
第k个排序实际上对应这其中的k'=(k-1)mod(n-1)!+1个排序。
这样一来,我们就把原问题转化成了一个完全相同但规模减少1的子问题:
求
[1,n]\a1这n-1个元素组成的排列中,第k'小的排序
/**
* 逆康托展开
* @desc 时间复杂度 O(N^2) 空间复杂度 O(N)
* @param n {number}
* @param k {number}
* @return {string}
*/
export function getPermutation2(n: number, k: number): string {
// 阶乘数组 - [1, 1, 4, 24]
const factorial = [1];
for (let i = 1; i < n; i++) {
factorial[i] = factorial[i - 1] * i;
}
// 记录已使用过的数字 - 下标代表数字
const valid: boolean[] = new Array(n + 1).fill(false);
let ans = '';
k--;
for (let i = 1; i <= n; i++) {
// a = ⌊(k - 1) / (n - 1)!⌋ + 1 算出第nth个排列
let a = Math.floor(k / factorial[n - i]) + 1;
// 找出该排列的数组(重点在于去重)
for (let j = 1; i <= n; j++) {
// 判断数值是否使用过
if (valid[j]) continue;
// 计数
a--;
// 找到对应数值
if (a === 0) {
ans += j;
valid[j] = true;
break;
}
}
// 取余更新k
k %= factorial[n - i];
}
return ans;
}