原题地址(中等)
方法1—数学
经力扣评论区网友提醒,我这里所用的思路叫做逆康托展开
思路
利用排列组合的性质,对此题进行数学方面的分析,是可以得到解决的。
我们先讲一下排列组合相关的知识,来做一个思想上的铺垫。
n=8
时有多少种排列呢?我们都知道,有n!
种排列,最小为1,2,3,4,5,6,7,8
,最大为8,7,6,5,4,3,2,1
。- 那么当最高位的数固定为
1
时,有多少种排列呢?我们也很容易就得出来,是(n-1)!
种排列,最小为1,2,3,4,5,6,7,8
,最大为1,8,7,6,5,4,3,2
- 那么当最高位的数固定为
2
时,有多少种排列呢?还是(n-1)!
种 - 最高位为
1
对应的是第1
种~第(n-1)!=6
种排列,依次类推,最高位为2
对应的是第(n-1)!+1=7
种~第2(n-1)!=12
种排列,最高位为3
对应的是第2``(n-1)!+1=13
种~第3``(n-1)!=18
种排列。想必大家已经发现规律了,我们就借助这个规律来解题。
回到题目上来,我们怎么求出第 k
个排列?借助上面的思路,我们用一个递归的过程来求解,以 n=4 ,k=7
为例:
- 固定最高位为
1
不变,有(n-1)!=6
种排列,固定最高位为2
不变,也有(n-1)!=6
种排列,所以k=7
一定是以2
开头的。所以我们找到了最高位2。 - 还剩3个数:1,3,4。更新
k -= 1*(n-1)!=3
,n -= 1
。问题就变成了nums=[1,3,4], k=3
。以同样的方法找当前最高位,为1
。 - 还剩2个数:3,4。更新
k -= 1*(n-1)! =1
,n -= 1
,问题就变成了nums=[3,4], k=1
。
我说的有点啰嗦emmmm
实在看不懂的话照着下面代码模拟一遍流程就能懂了
代码
class Solution {
public:
int mul(int num){
// 返回num的阶乘
int ans = 1;
for(int i=1; i<=num; i++) ans *= i;
return ans;
}
string getPermutation(int n, int k) {
vector<int> v; // 存放当前的数字
for(int i=1; i<=n; i++) v.push_back(i);
string ans = "";
while(n){
int cur_sum = mul(n-1); //当前阶乘
// k-1是边界的一个处理,比如n=3,k=4时,因为用的是闭区间[],不减一的话边界统一不起来,会成为[)
int t = (k-1) / cur_sum; //当前最高位要填v[t]
ans += ('0'+v[t]);
v.erase(v.begin()+t);
k -= (t * cur_sum);
n--;
}
return ans;
}
};
递归版:
class Solution {
public:
vector<int> v;
int* factorial;
string ans ;
void calFactorial(int n){
factorial = new int[n+1];
factorial[0] = 1;
for(int i=1; i<=n; i++) factorial[i] = factorial[i-1] * i;
}
void dfs(int i, int n, int k){ //i表示为v的下标
if(v.empty()) return;
if(k >= 0 && k <= factorial[n-1]){
ans += '0'+v[i];
v.erase(v.begin()+i);
dfs(0, n-1, k);
}
else
dfs(i+1, n, k-factorial[n-1]);
}
string getPermutation(int n, int k) {
calFactorial(n);
for(int i=1; i<=n; i++) v.push_back(i);
ans = "";
dfs(0, n, k);
return ans;
}
};
时空复杂度
while循环里面套了一个求阶乘的函数,所以时间复杂度为 O(N^2)
空间复杂度为 O(N)