题目:
给出集合 [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"
思路:直接看了网上大神的代码。按照程序可以一步步推导出结果,但是原理还是不太懂。。。。。。这个代码我看了下好像是类似于康拓展开。康拓展开是一个全排列的自然数的双射,实质是计算当前排列在所有由小到大全排列中的顺序。逆康拓展开可以求出全排列中给定顺序的排列。
以下是摘自百度百科的一个例子:
在(1,2,3,4,5)的全排列中求出顺序为61的组合(即34152)。由上述的计算过程可以容易的逆推回来,具体过程如下:
用 61 / 4! = 2余13,说明 说明比首位小的数有2个,所以首位为3。
用 13 / 3! = 2余1,说明,说明在第二位之后小于第二位的数有2个,但是因为3已经被放在首位,所以第二位为4。
用 1 / 2! = 0余1,说明,说明在第三位之后没有小于第三位的数,所以第三位为1。
用 1 / 1! = 1余0,说明,说明在第二位之后小于第四位的数有1个,所以第四位为5。
最后一位自然就是剩下的数2。
通过以上分析,所求排列组合为 34152
代码:
class Solution {
public String getPermutation(int n, int k) {
int[] factorial = new int[n];
//因式分解需要的基数 即每一步应该除去的阶乘
for (int i = 0; i < n; i++) {
if (i == 0) {
factorial[i] = 1;
continue;
}
factorial[i] = factorial[i - 1] * (i);
}
StringBuilder res = new StringBuilder(); %存放最后结果
boolean[] used = new boolean[n]; %用来标记数据,以便确定哪一个数已经被使用过了
int i = n - 1;
while (i >= 0) {
int digit = (k - 1) / factorial[i];//变换关系k-1 用来确定每一位的数,小于该数的数字的个数为digit
res.append(findKth(used, digit));//先取最高位的值 确定是哪一个数,排除已经出现过的
k -= digit * factorial[i--]; %确定下一个数
}
return res.toString();
}
//再次强调下,数组是用的地址,而我们传递的对象就是普通的参数
public int findKth(boolean[] used, int digit) {
int res = -1;
while (digit >= 0) {
if (!used[++res]) { //从小到大的去取值,同时进行标记
digit--;
}
}
used[res] = true;
return res + 1;//从0-4,变为1-5
}
}
执行最快的代码:
最快的代码还是康拓展开的思想,只不过细节的处理不同。
class Solution {
public String getPermutation(int n, int k) {
StringBuilder sb=new StringBuilder();
int num=k;
int factorial=calFactorial(n);
StringBuilder s=new StringBuilder();
for(int i=1;i<=n;i++){
s.append(i);
}
for(int i=n;i>=1;i--){
factorial/=i;
int index=(num-1)/factorial;
String tmp=""+s.charAt(index);
sb.append(tmp);
num-=index*factorial;
s=s.deleteCharAt(index);
}
return sb.toString();
}
public int calFactorial(int n){
int ans=1;
for(int i=1;i<=n;i++){
ans*=i;
}
return ans;
}
}