持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
排列序列
给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
- "123"
- "132"
- "213"
- "231"
- "312"
- "321"
给定 n 和 k,返回第 k 个排列。
解析
n位的全排列的所有排列情况为 n x (n-1) x (n-2)..... 1 = n!,即n的阶乘。通过式子可以知道,第一位的数字有n种选择,假设选择的第一位为2,那么以2为开头的全排列就有(n - 1)!个,事实上,以其它数字为开头的同样也有(n-1)!个,即n个(n-1)!。
假设n=3时,我们要找到k=4的全排列。那么全排列的第一个数字通过⌈k/(n-1)!⌉可得出为位置2,即2,但这个算式不是最终的算法,只是便于理解。排列中的数字只能使用一次,因此在选择数字中我们可以使用布尔数组hasUsed来记录以用的数据,同时方便计算。
由上可知n=3,k=4时的第一位是2,我们在hasUsed数组中标记3已使用,即我们要从[1,3]选择第二位。对于[1,3]来说,n=2,k=2,根据之前的步骤可得第二位为下标为2的3,同理可得第三位为1。
以上只是逻辑上的解释,真实的代码比较抽象,需要一定的理解能力。
代码
class Solution{
//记录数字是否使用过
private boolean[] hasUsed;
//阶乘数组
private int[] factorial = new int[]{1,1,2,6,24,120,720,5040,40320,362880};
private int n;
//当前递归层数的k
private int k;
public String getPermutation(int n, int k) {
//初始化操作
this.n = n;
this.k = k;
// 记录已使用的数组,效果是减少“n”
hasUsed = new boolean[n + 1];
StringBuilder result = new StringBuilder();
dfs(0, result);
return result.toString();
}
private void dfs(int level, StringBuilder result) {
//递归的结束
if (level == n) {
return;
}
// 找到对应递归层数的"(n-1)"!
int cnt = factorial[n - 1 - level];
for (int i = 1; i <= n; i++) {
if (hasUsed[i]) {
continue;
}
if (cnt < k) {
//位数减去阶乘的位置
k -= cnt;
continue;
}
//添加的数字一定正确
result.append(i);
hasUsed[i] = true;
dfs(level + 1, result);
return;
}
}
}
记录阶乘的代码
//打表存储,提高效率,减少计算 {1,1,2,6,24,120,720,5040,40320,362880}
private void calculateFactorial(int n) {
int[] factorial = new int[n + 1];
factorial[0] = 1;
for (int i = 1; i <= n; i++) {
factorial[i] = factorial[i - 1] * i;
}
return factorial;
}