60. Permutation Sequence(第k个排列)解法(C++ & 注释)

1. 题目描述

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

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

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

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

说明:

  • 给定 n 的范围是 [1, 9]。
  • 给定 k 的范围是[1, n!]。

示例 1:

输入: n = 3, k = 3
输出: “213”

示例 2:

输入: n = 4, k = 9
输出: “2314”

题目链接:中文题目英文题目

2. 分组查找(Search by Groups)

2.1 解题思路

虽然这道题可以用列举所有排列组合来解题,但是非常耗时,所以我们需要换一个思路。回想起上一题46. Permutations(全排列)中,我们使用了两种方法:1)递归;2)序号插入;观察两种方法,可以发现都是先固定一个数,然后继续后面排列组合剩余的数。所以针对这道题,我们可以顺着这个思路进行思考,以n = 4, k = 24为例来进行说明:

  1. 1234
  2. 1243
  3. 1324
  4. 1342
  5. 1423
  6. 1432
  7. 2134
  8. 2143
  9. 2314
  10. 2341
  11. 2413
  12. 2431
  13. 3124
  14. 3142
  15. 3214
  16. 3241
  17. 3412
  18. 3421
  19. 4123
  20. 4132
  21. 4213
  22. 4231
  23. 4312
  24. 4321

按照先前提到的思路,首先观察左起第一个数,我们发现4个数每个重复了6次;接下来,固定第一个数(比如4):

  1. 4123
  2. 4132
  3. 4213
  4. 4231
  5. 4312
  6. 4321

我们观察第二个数(或者除去被固定的4之后,第一个数),发现3个数每个重复了2次,接着,再固定第二个数(比如3):

  1. 4312
  2. 4321

我们观察第三个数,发现2个数每个重复了1次;再固定第三个数,之后1个数每个重复了1次。如果以每次固定的组合为分组,那么n个数的全排列(n!),每组有(n - 1)!个数,一共有n! / (n - 1)!个组。因为序号是从0开始的,所以第几个组的序号为:n! - 1 / (n - 1)!。这个规律很好理解,n个数有n!个组合,那么固定一个数相当于从n个数里面减一,那么剩余的n - 1个数进行全排列,必然有(n - 1)!个组合,而且每个组的序号对应n个数对应的序号,相当于用第一个数来进行映射,如下图所示:

在这里插入图片描述

也就是说,我们可以对全排列进行分组,然后在组里面用给定的k - 1进行查找。那接下来,我们来看看如何用这个思路进行查找。

n = 4,一共4个数进行排列。第一类分组一共有4! / (4 - 1)! = 23 / 6 = 4个组,每组3! = 6个组合,所以根据这个信息,当k = 24,既查找序号为23(m)的组合。m在第几组呢?以每组6个来算,m在第3(= 23 / 6)个组里面,所以选取4作为第一个数。这一步我们也求得了m在第一类分组里面的序号:m - 3 * 6 = 5;所以直接用m % 6即可得到一样的结果。后面依次对第二类分组重复上述的步骤进行查找即可得到最后的答案。

最后需要解释一下下映射数组:string nums(“123456789”)。如果n < 9,这样做不会选取到后面不需要的数么?

依据上面得到的规律:n个数的最大序号为n - 1,且有n!个组合,每个分组有(n - 1)!个组合,那么k最大取到n!,即最大序号为n! - 1,所以每次映射得到的最大序号为n! - 1 / (n - 1)! = n! / (n - 1)! - 1 / (n - 1)! = n - 1 / (n - 1)! <= n - 1,所以不会取超过n - 1序号后面的数,即不可能取到后面不需要的数。所以上面的初始化是木有问题哒~

2.2 实例代码

class Solution {
public:
    string getPermutation(int n, int k) {
        string ans;
        string nums("123456789");
        vector<int> factorials(n, 1);

        for (int i = 1; i <  n; i++) factorials[i] = factorials[i - 1] * i; // 初始化n的阶乘数组

        k--;
        for (int i = n; i > 0; i--) {
            int idx = k / factorials[i - 1];
            k %= factorials[i - 1];
            ans.push_back(nums[idx]);
            nums.erase(nums.begin() + idx, nums.begin() + idx + 1);
        }

        return ans;
    }
};

3. 参考资料

  1. [LeetCode] 60. Permutation Sequence 序列排序

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值