LeetCode60-第k个排列
给出集合 [ 1 , 2 , 3 , … , n ] [1,2,3,…,n] [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)从右往左找出第一个:顺序对
j = m a x ( i ∣ n u m [ i ] ) < n u m [ i + 1 ] j={max(i|num[i])<num[i+1]} j=max(i∣num[i])<num[i+1]
(2)从 n u m [ j ] num[j] num[j]的位置开始,往右找到第一个大于 n u m [ j ] num[j] num[j]的数: n u m [ k ] num[k] num[k]
k = m i n ( i ∣ n u m [ i ] > n u m [ j ] 并 且 i > j ) k={min(i|num[i]>num[j]并且i>j)} k=min(i∣num[i]>num[j]并且i>j)
(3)交换 n u m [ k ] num[k] num[k]与 n u m [ j ] num[j] num[j]的位置
(4)将 j j j之后的数组进行翻转:
原来是:
n
u
m
[
j
+
1
]
.
.
.
.
n
u
m
[
k
]
.
.
.
.
n
u
m
[
n
]
num[j+1]....num[k]....num[n]
num[j+1]....num[k]....num[n]
翻转后:
n
u
m
[
n
]
n
u
m
[
n
−
1
]
.
.
.
.
n
u
m
[
k
]
.
.
.
.
n
u
m
[
j
+
1
]
num[n]num[n-1]....num[k]....num[j+1]
num[n]num[n−1]....num[k]....num[j+1]
(5)组合起来即得到下一个排序
这个方法显然是不适用于这道题,我们知道k最大为n!,此时这个算法的时间复杂度将会超出你的想象
(二)算数技巧
这道题本质上是一个算数的问题。
我们知道以
i
i
i 开头的序列有:(n-1)! 个
并且以
(
i
+
1
)
(i+1)
(i+1)开头的序列必然会大于以
i
i
i 开头的序列
所以我们知道了:
k
k
k 如果属于
[
1
,
(
n
−
1
)
!
]
[1,(n-1)!]
[1,(n−1)!] ,那么它的排列的第一个数字一定是:1
k
k
k 如果属于
[
(
n
−
1
)
!
+
1
,
2
∗
(
n
−
1
)
!
]
[(n-1)!+1,2*(n-1)!]
[(n−1)!+1,2∗(n−1)!] ,那么它的排列的第一个数字一定是:2
…
因此,我们通过这样的方法,来缩小排列的范围
在确定了第一个范围之后,第二个范围也能用同样的方法确定下来,一直到第n个数确定下来
从上面的描述可以知道,这是一个递归求解的方案。
C++代码
class Solution {
public:
string s;
string getPermutation(int n, int k) {
string sum = "123456789";
string nums = sum.substr(0, n);
recursion(k, n, nums);
return s;
}
void recursion(int k, int n, string nums) {
if (n == 0)
return;
int fact = factorial(n - 1);
int m = (k - 1) / fact;
n--;
s.push_back(nums[m]);
nums.erase(m, 1);
k -= m * fact;
recursion(k, n, nums);
}
int factorial(int n) {
int s = 1;
for (int i = 1; i <= n; i++)
s *= i;
return s;
}
};
执行效率: