原题地址(中等)
方法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);}elsedfs(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)
