这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战
前言
力扣第六十题 排列序列
如下所示:
给出集合 [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]
所有排列中的第 K
个位置的排列
看到题目后我大概的思路分为以下两个步骤:
- 递归生成所有的排列,每生成一种排列让计数
count++
- 当
count == k
时,返回当前的结果
伪代码如下所示:
int count = 0;
String ret = "";
public String getPermutation(int n, int k) {
dfs("", n, k);
return ret;
}
public void dfs( String path, int n, int k) {
if (path.length() == n) {
count++; ret = path;
return;
}
for (int i=1; i<n+1; i++) {
if (count == k) return;
if (path.contains(i+"")) continue;
dfs(path + i, n, k);
}
}
直达在我碰到了 n = 9, k = 353955
的测试用例,试了五遍左右,一直会超时。
不过我有一个疑惑,这个代码在力扣上会超时,但是在我本地是不会有这个问题的。我猜测可能是力扣上面对时间要求更严格吧。
既然递归无法解决此问题,那么就得另辟蹊径了。在看了超过十遍力扣的官方解法后,很悲惨的发现看不懂官方说的数学 + 缩小问题规模解法。
于是我就问了我的女朋友,它告诉我了一个非常重要的规律:
因为选择数的过程是从左往右,从小往大的,所以第k个排列的第一个数为第 (k-1)/(n-1)! + 1
小的数,后面的数也可以相同处理
这句话怎么理解呢?
假设第
k
个排列为{a1, a2, a3, ..., an}
我们知道以 ai
作为第一个数的可能有 (n-1)!
种,所以:
- 第一个数为第
P1 = (k-1)/(n-1)! + 1
(向上取整,最小值为1)小的数,此处的p
表示未被选择过的数种从左到位选择的位置 - 那么第二个数为第
P2 = k - (P1-1)*(n-1)!
小的数,因为要减去以a1
为第一个数的所有可能 - ...
- 第
n
个数为第Pn = k - (P'n-1' - 1)*(0)!
小的数,要减去以an-1
为第一个数的可能
举个例子
以 n=4
,k=9
作为例子
- 第一个数的位置为
P1 = 2
,故选择未选择种第2
个小的数2
- 我们在这里相当于处理
n = 3
,k = 3
(数组为[1, 3, 4]
) 的第一个数应该选什么?此时P2 = 2
,故选择3
- 此时处理
n=2
,k=1
(数组为[1, 4]
),可得P3 = 1
,故选择1
- 此时处理
n=1
,k=1
(数组为[4]
),可得P4 = 1
,故选择4
- 最终第
9
个排列为2314
综上所述,我们永远只关心第一个数是什么(对于后面的数来说,也是要处理子排列种的第一个数)
二、实现
实现代码
public String getPermutation(int n, int k) {
StringBuilder ret = new StringBuilder();
int[] factorial = new int[n]; // 0 ~ (n-1) 对应的阶乘
factorial[0] = 1;
for (int i = 1; i < n; ++i) {
factorial[i] = factorial[i - 1] * i;
}
boolean[] selected = new boolean[n+1];
while (ret.length() < n) {
// 当前应当选择第几大的数
int p = (k - 1) / factorial[n-1-ret.length()] + 1;
k = k - (p -1) * factorial[n-1-ret.length()];
int count = 0;
for (int i=1; i<selected.length; i++) {
if (selected[i])
continue;
count++;
if (count == p) {
ret.append(i);
selected[i] = true;
break;
}
}
}
return ret.toString();
}
测试代码
public static void main(String[] args) {
new Number60().getPermutation(4, 9);
}
结果
三、总结
感谢看到最后,非常荣幸能够帮助到你~♥