leetcode 60.第k个排列
题目描述
给出集合 [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"
解题思路
本题可以选择使用回溯法解题,但是时间复杂度太高,使用数学的思维去解题,下面分享一下解题思路。
给定一个数字n和一个数字k,那么如果可以直接求出所有的组合,那么直接就可以使用下标索引的方式直接找到第k个排列,那么又是是如何找到所有的排序的。即首先固定第一位,有n种可能;固定第二位有n-1中可能;…;第n位有1种可能。那么说了那么多如何直接计算第k个呢?首先,如果固定了第一位的数字,那么剩下的没有排列的n-1位,可以有(n-1)!种排列方式,如果在固定第一位的基础上固定第二位,那么剩下的未排列的n-2位就有(n-2)!种排列方式。通过这样的计算,给定一个k之后,就可以确定每一个位对应什么数字。这样说可能不是很明白,那么以题中的示例2举例,直接计算出第k个排列
例子: n = 4, k = 9
- 首先计算第一位,第一位可以选择1、2、3、4四个数,选定以后每一个数字对应的是(n-1)!种排列,由于是按照顺序排列的,如果第一位选择1,那么排完1就可以产生6个排列,即第六个就是最有一个以1作为开头的排序,同理排完2,就会产生12个排列,那么给定一个数字k,就可以确定第一位是对应的数字了。例题中给的k=9,那么 9/6 = 1.5,这里如果不是整除,直接向上取整。即9/6=2;那么第一位对应的数字就是2
- 然后是确定第二位,由于数字2已经使用了,所有要把它删除,第二位可以使用的数字是1、3、4。这时的k也是需要改变的,确定了第一个数字以后,可以知道目标排列就在以数字2开头的排列中,由于是顺序排列的所以,需要减去数字1开头的排序,k = k-((n-1)!) * num,这里的num是指的第几个数字,这里是数字1,那么如果我们的目标是3开头的排序,就需要减去1、2开头的所有排列,即 k = k-((n-1)!) * 2. 所有k = 9 - 1*((4-1)!) = 3,此时n=3,第二位数字是3/((3-1)!) = 2,这时未排序的数组中只有1、3、4,所以第二个数字是3
- 第三个数字参考上面两步,先计算现在的k = 3 - 1*((3-1)!) = 1, 此时的n = 2 ,第三个数字的下标是 1/((2-1)!) = 1,此时数组里面的数字是1、4,所以第三个数字是1
- 第四个数字,其实已经拍完了,就剩一个数字4了。同样的我们继续按照上面的方式计算一下,k = 1- 0*((2-1)!),此时的n=1; 我们定义 数字0的阶乘等于1,所以第四个数字为 1/((1-1)!) = 1,此时数组里面只有数字4
- 所以第9个排列是 2314
class Solution {
public:
string getPermutation(int n, int k) {
string str = "123456789";
string res = "";
while(n){
int num = 0; // 用于表示计算对应位,应该选择哪一个数字
if(k%factorial(n-1)){
num = k/factorial(n-1) +1;
}
else{
num = k/factorial(n-1);
}
res += str[num-1]; // 添加到字符串
str.erase(str.begin()+num-1); // 使用过的字符串从待定的字符串中删除
k -= factorial(n-1)*(num-1); // 计算新的k
n--; // 确定了一位数字以后,n的数量要减少1
}
return res;
}
// 阶乘
int factorial(int n){
if(n == 0){
return 1;
}
else{
return n*factorial(n-1);
}
}
};
欢迎大家关注我的个人公众号,同样的也是和该博客账号一样,专注分享技术问题,我们一起学习进步