算法题目 -- 排列序列

68 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

排列序列

给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

  1. "123"
  2. "132"
  3. "213"
  4. "231"
  5. "312"
  6. "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;
}