这是我参与8月更文挑战的第23天,活动详情查看:8月更文挑战
题目
60. 排列序列
给出集合 [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"
提示:
1 <= n <= 9 1 <= k <= n!
方法签名
public String getPermutation(int n, int k)
分析
先分析一下题目:
n为给出的0-9的数,代表的是1-n的数字集合,我们要做的是从这些数字集合组成的数,取出第k小的数作为结果返回。
我们知道:
- 根据给定的n,总共可以组成n!个不同的数,如果都列出来再取数,会浪费很大一部分时间。
我们换一个思路,看看能否减少我们的计算量。
-
我们知道
- 将1-n n个数组成n位数并且需要用上所有的数字,越大的数放在越小的位置,结果会越小。----------(1)
那么我们可以试着通过题目所要的k,来确定我们的部分数字排列情况。
就如题目给定的:n=3,k=3来试一下:
-
我们知道n=3时,一共有3!种排列,即6种排列,其实可以分为:
-
以1开头的:1XX,这部分在总的排列里,在第1到第2个。( (3-1)!=2)
-
以2开头的:2xx,这部分k在3-4内,复合题目要求。
-
那么我们确定2的位置,接下来来确定1和3应该放在哪个位置:
根据常识(1),容易推出1放在第二个位置恰好是第3个,那么我们的答案
213
就可以返回了。
-
-
那么我们抽象一下,给定n和k,那么:
- 先确定第1位为i:确定k在[(i-1)X(n-1)!,(iX(n-1)!]的范围内。
- 然后确定第2位为j:确定k在以 (i-1)*(n-1)! + (j-1)*(n-2)! 为左闭界,i(n-1)!* + (j)*(n-2)! 为右闭界的范围内。
- 然后接下去按照给定的左边界,继续探索k的位置,直到恰好落在某个端点上的时候。
我们先确定了最基本的算法框架,假定它是可以AC的,那么接下来我们还缺少一点点小细节:
-
当k已经落在了某个端点,但我们还没有把所有数都填上的时候,我们需要怎么记?
因此我们需要有一个数组来维护剩下的数字,等到k落在某个端点了,我们把剩下的数字按顺序添加到后面即可。
代码
public static String getPermutation(int n, int k) {
List<Integer> candy = new LinkedList<>();
for (int i = 0; i <= n; i++) {
candy.add(i);
}
char[] resC = new char[n];
int idx = 0;
while(k>1){
int curF = factorial(n-1);
int comp = (k%curF!=0?1:0);
n--;
int num = (k/curF)+comp;
int toD = candy.remove(num);
resC[idx++] = (char)(toD + '0');
k -= (num-1)*curF;
}
while(candy.size()>1){
resC[idx] = (char)(candy.get(1)+'0');
candy.remove(1);
idx++;
}
return new String(resC);
}
/**
* 获取n的阶乘的值
*/
public static int factorial(int n){
int t = 1;
while(n>0){
t = t*n;
n--;
}
return t;
}
\