【题目】*60. 第k个排列
给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"
给定 n 和 k,返回第 k 个排列。
说明:
给定 n 的范围是 [1, 9]。
给定 k 的范围是[1, n!]。
示例 1:
输入: n = 3, k = 3
输出: "213"
示例 2:
输入: n = 4, k = 9
输出: "2314"
【解题思路1】数学
class Solution {
public String getPermutation(int n, int k) {
int[] factorial = new int[n];
factorial[0] = 1;
for (int i = 1; i < n; ++i) {
factorial[i] = factorial[i - 1] * i;
}
--k;
StringBuffer ans = new StringBuffer();
int[] valid = new int[n + 1];
Arrays.fill(valid, 1);
for (int i = 1; i <= n; ++i) {
int order = k / factorial[n - i] + 1;
for (int j = 1; j <= n; ++j) {
order -= valid[j];
if (order == 0) {
ans.append(j);
valid[j] = 0;
break;
}
}
k %= factorial[n - i];
}
return ans.toString();
}
}
【解题思路2】全排列
所求排列 一定在叶子结点处得到,进入每一个分支,可以根据已经选定的数的个数,进而计算还未选定的数的个数,然后计算阶乘,就知道这一个分支的 叶子结点 的个数:
- 如果 k 大于这一个分支将要产生的叶子结点数,直接跳过这个分支,这个操作叫「剪枝」;
- 如果 k 小于等于这一个分支将要产生的叶子结点数,那说明所求的全排列一定在这一个分支将要产生的叶子结点里,需要递归求解。
class Solution {
boolean[] used; //记录数字是否使用过
int[] factorial; //阶乘数组
public String getPermutation(int n, int k) {
calculateFactorial(n);
// 查找全排列需要的布尔数组
used = new boolean[n + 1];
Arrays.fill(used, false);
StringBuilder path = new StringBuilder();
dfs(0, path, n, k);
return path.toString();
}
/**
* @param index 在这一步之前已经选择了几个数字,其值恰好等于这一步需要确定的下标位置
* @param path
*/
public void dfs(int index, StringBuilder path, int n, int k) {
if (index == n) {
return;
}
// 计算还未确定的数字的全排列的个数,第 1 次进入的时候是 n - 1
int cnt = factorial[n - 1 - index];
for (int i = 1; i <= n; i++) {
if (used[i]) {
continue;
}
if (cnt < k) {
k -= cnt;
continue;
}
path.append(i);
used[i] = true;
dfs(index + 1, path, n, k);
// 注意 1:没有回溯(状态重置)的必要
// 注意 2:这里要加 return,后面的数没有必要遍历去尝试了
return;
}
}
// 计算阶乘数组
public void calculateFactorial(int n) {
factorial = new int[n + 1];
factorial[0] = 1;
for (int i = 1; i <= n; i++) {
factorial[i] = factorial[i - 1] * i;
}
}
}