60. Permutation Sequence

题目:

The set [1,2,3,…,n] contains a total of n! unique permutations.

By listing and labeling all of the permutations in order,
We get the following sequence (ie, for n = 3):

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

Given n and k, return the kth permutation sequence.

Note: Given n will be between 1 and 9 inclusive.

链接:  http://leetcode.com/problems/permutation-sequence/

题解:

数学题,找到第k个permutation sequence。 (题外话 : 现在经常体会到数学学得不够好会有多吃力,但也不必要为了钻牛角尖去做个数学家,要足够好,够用就可以了。但怎样算足够好,怎样算够用,很难界定啊。)

n!可以被分为n组,每组有(n - 1)!个数字。 假设k在1 ~ n!的范围内,则凭借k / (n - 1)! 我们可以从找到k中有多少组(n - 1)!,而 k %= (n - 1)!则可以把k映射到 1 ~ (n - 1)!的范围内,我们可以继续使用k / (n - 2)!来找到第二大的数字,以此类推。需要思考的地方是使用一个有效的数据结构,来获得当前的最大数字,以及所剩下最小的数字。我觉得可以使用min heap。 这里因为n为1 ~ 9,为简便使用了linkedlist。

还有一个很巧妙的地方是 k--,这样k / (n - 1)!就对应着从0 ~ (n - 1)这n个group,这个结果也是我们linkedlist中取出元素的index。接下来进行 k % = (n - 1)!后, k又继续对应 0 ~ (n - 2)这个 n - 1个group。非常好用。

Time Complexity - O(n), Space Complexity - O(n)。

public class Solution {
    public String getPermutation(int n, int k) {
        if(n <= 0)
            return "";
        List<Integer> list = new LinkedList<Integer>();
        int factorial = 1;
        
        for(int i = 1; i <= n; i++) {
            list.add(i);
            factorial *= i;
        }
        
        k--;                                            // crucial step, put k into range 0 - (n! - 1)
        StringBuilder sb = new StringBuilder();
        
        while(n > 0) {    // n digits
            factorial /= n;     //get (n - 1)!;
            sb.append(list.remove(k / factorial));       //  k / factorial = most important digits
            k %= factorial;                              //  k % factorial = reflect k to range 0 - ((n - 1)! - 1)
            n--;
        }
        
        return sb.toString();
    }
}

二刷:

二刷的作用,就是清理一刷糊弄过去的题目。这道题目也是卡。 主要还是参考了discuss里面Adeath以及melvin.ming.gong的解答。以下的解释很烂,三刷希望总结得好一点。

思路主要是,我们有1 ~ n个字母可以来做全排列,那么假设k是存在于 1到 n!中的一个数字,这个数字就会对应我们的一个全排列字母组合,也就是kth permutation sequence。

下面我们来分析一下全排列每个数字的构成:

  1. 假如k = n!,那么结果肯定是9876543421,最大的全排列。 最高位的确定是 (k- 1) / (9 - 1)!,就是最高位的数字,是由这个数字的排位k - 1 除以低一位的全排列可能性 (n - 1)!来确定的。
  2. 我们先从找到最大的全排列 factorial = n!, 
  3. 为什么要k - 1。 假设我们有一个linkedlist,这个list包含所有数字的集合 -  1, 2, 3, 4, 5, 6, 7, 8, 9。因为链表的开头是从0 开始, 我们每次从链表中移除的话,要shift一位。这样 k % (n - 1)!就可以被映射到到 0 到 (n - 1)!这块bucket里。否则我们还要判断 k % (n - 1)是否为0,写一些额外的code
  4. 当我们求出最高位的数字 n1之后,那么我们接下来要做的就是,在剩下的数字里,我们更新 factorial /= n, 然后找到在linkedlist里面位置是  k / factorial的数字,再将其移除就可以了。
  5. 最好加上一些判断,比如 k <= 0的情况, 或者k > n!的情况。

Java:

Time Complexity - O(n2), Space Complexity - O(n)。   O(n2)因为我们用到了linekedlist的remove。假如 n属于 1 ~ 9的话其实可以认为是O(1), O(1)。

public class Solution {
    public String getPermutation(int n, int k) {
        if (k <= 0) {
            return "";
        }
        List<Integer> nums = new LinkedList<>();
        int factorials = 1;
        for (int i = 1; i <= n; i++) {
            nums.add(i);
            factorials *= i;
        }
        while (k > factorials) {
            if (k % factorials == 0) {
                k = factorials;
            } else {
                k %= factorials;
            }
        }
        StringBuilder sb = new StringBuilder();
        for (k--; n > 0; n--) {
            factorials /= n;
            sb.append(nums.remove(k / factorials));
            k %= factorials;
        }
        return sb.toString();
    }
}

三刷:

遇到这题还是会卡一点。应该是因为以前思路不够清晰吧。

我们主要是使用以下几个步骤:

  1. 先求得[1, n]的factorial, 同时创建一个linkedlist,node分别为[1, n]
  2. k--, 将k从 1-based转换为0-based,方便以后我们的模操作。
  3. 这里可以加一步判断,假如k >= n那么我们返回一个特定的字符串。
  4. 在n > 0的时候
    1. 我们先把factorial减少到(n - 1)!
    2. 然后我们计算k 中有多少个(n - 1)!,把这个数字作为linkedlist中的index找到相应的节点,删除这个节点并且将其值加到sb中
    3. 使用 k % factorial将 k映射到 (n - 1)!的范围内
    4. n--, 继续下一次操作。
  5. 返回结果sb.toString();

Java:

Time Complexity - O(n), Space Complexity - O(n)。  其实因为n只在1 ~ 9中,所以我们可以假定时空间复杂度都是O(1)

public class Solution {
    public String getPermutation(int n, int k) {
        if (k <= 0 || n <= 0) return "";
        List<Integer> nums = new LinkedList<>();
        int factorial = 1;
        for (int i = 1; i <= n; i++) {
            factorial *= i;
            nums.add(i);
        }
        StringBuilder sb = new StringBuilder();
        k--;
        while (n > 0) {
            factorial /= n;
            sb.append(nums.remove(k / factorial));
            k %= factorial;
            n--;
        }
        return sb.toString();
    }
}

Reference:

https://leetcode.com/discuss/16064/an-iterative-solution-for-reference

https://en.wikipedia.org/wiki/Lehmer_code

https://en.wikipedia.org/wiki/Permutation

https://leetcode.com/discuss/42700/explain-like-im-five-java-solution-in-o-n 

原文地址:https://www.cnblogs.com/yrbbest/p/4436392.html