题目描述
给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
- “123”
- “132”
- “213”
- “231”
- “312”
- “321”
给定 n 和 k,返回第 k 个排列。
示例 1:
输入:n = 3, k = 3
输出:"213"
示例 2:
输入:n = 4, k = 9
输出:"2314"
示例 3:
输入:n = 3, k = 1
输出:"123"
题解思路
方法一:调用 STL 库
代码实现:
class Solution {
public:
string getPermutation(int n, int k) {
string s = string("123456789").substr(0, n);
for (int i = 1; i < k; ++i) {
next_permutation(s.begin(), s.end());
}
return s;
}
};
方法二:除法定位
例如: n = 6, k = 373;
初始化数组 nums = [1, 2, 3, 4, 5, 6]; v = {1, 1, 2, 6, 24, 120, 720, 5040, … };
首先应该明白,以 1 开头的全排列有 5! 个,以 2 开头的全排列有 5! 个 …… 共 5! * 6 = 6! 个
- 故 k = 373 时,全排列的第一个数字应该是 nums[ k / 5! ] = 4 ;
- 数组删除 4, 此时 nums = [1, 2, 3, 5, 6]; k = k % 5! = 12 ;
- 接下来就是在 nums 中找第 12 个全排列,重复 1,2 步即可 。
代码实现:
class Solution {
public:
string getPermutation(int n, int k) {
vector<int> v(n);
v[0] = 1;
for (int i = 1; i < n; ++i) {
v[i] = i * v[i - 1];
}
--k;
string res;
vector<int> nums;
for (int i = 1; i <= n; ++i) {
nums.push_back(i);
}
for (int i = 1; i <= n; ++i) {
int order = k / v[n - i];
res.push_back(nums[order] + '0');
for (int j = order; j < n - i; j++) {
nums[j] = nums[j + 1];
}
k %= v[n - i];
}
return res;
}
};
代码实现:
class Solution {
int mul(int num) {
// 返回num的阶乘
int res = 1;
while (num > 1) {
res *= num--;
}
return res;
}
public:
string getPermutation(int n, int k) {
vector<int> v; // 存放当前的数字
for (int i = 1; i <= n; i++) {
v.push_back(i);
}
string ans = "";
--k;
while (n) {
int cur_sum = mul(n - 1); //当前阶乘
// k-1是边界的一个处理,比如n=3,k=4时,因为用的是闭区间[],不减一的话边界统一不起来,会成为[)
int t = k / cur_sum; //当前最高位要填v[t]
ans += ('0' + v[t]);
v.erase(v.begin() + t);
k -= (t * cur_sum);
n--;
}
return ans;
}
};
方法三:康托编码
采用康托编码的思路。其实就是康托展开的逆过程。康托展开用来求某个全排列数是第几小的数,也就是当这些数按顺序排时第几个数。
过程如下:比如求321 是 第几小的,可以这样来想:小于3的数有1和2 两个,首位确定之后后面两位有2!中情况,所以共有 2*2!=4种。
小于2的数只有一个1,所以有11!=1种情况,最后一位是1,没有比一小的数,所以是0!=0
综上:小于321的数有4+1=5个,所以321是第六小的数。
逆过程就是已知这个数是第k个数,求这个数是多少,当然是知道n的值的。
第k个数就是有k-1个数比这个数小。
所以就是 k-1=an * (n-1)! + an-1 * (n-2)!+…+a1*0!;
再举一个例子:
如何找出第16个(按字典序的){1,2,3,4,5}的全排列?
- 首先用16-1得到15
- 用15去除4! 得到0余15
- 用15去除3! 得到2余3
- 用3去除2! 得到1余1
- 用1去除1! 得到1余0
有0个数比它小的数是1,所以第一位是1
有2个数比它小的数是3,但1已经在之前出现过了所以是4
有1个数比它小的数是2,但1已经在之前出现过了所以是3
有1个数比它小的数是2,但1,3,4都出现过了所以是5
最后一个数只能是2
所以排列为1 4 3 5 2
代码实现:
class Solution {
// 得到 n 的阶乘
int factorial(int n)
{
int res = 1;
while (n > 1) {
res *= n--;
}
return res;
}
string kth_permutation(string& s, int k)
{
const int n = s.size();
string res;
int base = factorial(n - 1);
--k;
for (int i = n - 1; i > 0; k %= base, base /= i, --i)
{
auto a = s.begin() + k / base;
res.push_back(*a);
s.erase(a);
}
res.push_back(s[0]);
return res;
}
public:
string getPermutation(int n, int k) {
string s(n, '0');
for (int i = 0; i < n; i++)
{
s[i] += i + 1;
}
return kth_permutation(s, k);
}
};