力扣第六十题-排列序列

507 阅读3分钟

这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战

前言

力扣第六十题 排列序列 如下所示:

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

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

  1. "123"
  2. "132"
  3. "213"
  4. "231"
  5. "312"
  6. "321"

给定 n 和 k,返回第 k 个排列。

示例 1:

输入: n = 3, k = 3
输出: "213"

示例 2:

输入: n = 4, k = 9
输出: "2314"

示例 3:

输入: n = 3, k = 1
输出: "123"

一、思路

排列序列 这一题题目比较短,很容易理解。就是在返回集合 [1~n] 所有排列中的第 K 个位置的排列

看到题目后我大概的思路分为以下两个步骤:

  1. 递归生成所有的排列,每生成一种排列让计数 count++
  2. count == k 时,返回当前的结果

伪代码如下所示:

    int count = 0;
    String ret = "";
    public String getPermutation(int n, int k) {
        dfs("", n, k);
        return ret;
    }

    public void dfs( String path, int n, int k) {
        if (path.length() == n) {
            count++; ret = path;
            return;
        }
        for (int i=1; i<n+1; i++) {
            if (count == k) return;
            if (path.contains(i+"")) continue;
            dfs(path + i, n, k);
        }
    }

直达在我碰到了 n = 9, k = 353955 的测试用例,试了五遍左右,一直会超时。

不过我有一个疑惑,这个代码在力扣上会超时,但是在我本地是不会有这个问题的。我猜测可能是力扣上面对时间要求更严格吧。

既然递归无法解决此问题,那么就得另辟蹊径了。在看了超过十遍力扣的官方解法后,很悲惨的发现看不懂官方说的数学 + 缩小问题规模解法。

于是我就问了我的女朋友,它告诉我了一个非常重要的规律:
因为选择数的过程是从左往右,从小往大的,所以第k个排列的第一个数为第 (k-1)/(n-1)! + 1 小的数,后面的数也可以相同处理

这句话怎么理解呢?

假设第 k 个排列为 {a1, a2, a3, ..., an}

我们知道以 ai 作为第一个数的可能有 (n-1)! 种,所以:

  • 第一个数为第 P1 = (k-1)/(n-1)! + 1(向上取整,最小值为1)小的数,此处的 p 表示未被选择过的数种从左到位选择的位置
  • 那么第二个数为第 P2 = k - (P1-1)*(n-1)! 小的数,因为要减去以 a1 为第一个数的所有可能
  • ...
  • n 个数为第 Pn = k - (P'n-1' - 1)*(0)! 小的数,要减去以 an-1 为第一个数的可能

举个例子

n=4k=9 作为例子

  1. 第一个数的位置为 P1 = 2,故选择未选择种第 2 个小的数 2
  2. 我们在这里相当于处理 n = 3, k = 3(数组为 [1, 3, 4]) 的第一个数应该选什么?此时 P2 = 2,故选择 3
  3. 此时处理 n=2k=1(数组为 [1, 4]),可得 P3 = 1,故选择 1
  4. 此时处理 n=1k=1(数组为 [4]),可得 P4 = 1,故选择 4
  5. 最终第 9 个排列为 2314

综上所述,我们永远只关心第一个数是什么(对于后面的数来说,也是要处理子排列种的第一个数)

二、实现

实现代码

    public String getPermutation(int n, int k) {
        StringBuilder ret = new StringBuilder();
        int[] factorial = new int[n];   // 0 ~ (n-1) 对应的阶乘
        factorial[0] = 1;
        for (int i = 1; i < n; ++i) {
            factorial[i] = factorial[i - 1] * i;
        }
        boolean[] selected = new boolean[n+1];
        while (ret.length() < n) {
            // 当前应当选择第几大的数
            int p = (k - 1) / factorial[n-1-ret.length()] + 1;
            k = k - (p -1) * factorial[n-1-ret.length()];
            int count = 0;
            for (int i=1; i<selected.length; i++) {
                if (selected[i])
                    continue;
                count++;
                if (count == p) {
                    ret.append(i);
                    selected[i] = true;
                    break;
                }
            }
        }
        return ret.toString();
    }

测试代码

    public static void main(String[] args) {
        new Number60().getPermutation(4, 9);
    }

结果

image.png

三、总结

感谢看到最后,非常荣幸能够帮助到你~♥