60. Permutation Sequence(第k个排列)
1. 题目描述
给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
- “123”
- “132”
- “213”
- “231”
- “312”
- “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为例来进行说明:
- 1234
- 1243
- 1324
- 1342
- 1423
- 1432
- 2134
- 2143
- 2314
- 2341
- 2413
- 2431
- 3124
- 3142
- 3214
- 3241
- 3412
- 3421
- 4123
- 4132
- 4213
- 4231
- 4312
- 4321
按照先前提到的思路,首先观察左起第一个数,我们发现4个数每个重复了6次;接下来,固定第一个数(比如4):
- 4123
- 4132
- 4213
- 4231
- 4312
- 4321
我们观察第二个数(或者除去被固定的4之后,第一个数),发现3个数每个重复了2次,接着,再固定第二个数(比如3):
- 4312
- 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;
}
};