题目链接:
https://leetcode.com/problems/permutation-sequence/description/
描述
The set [1,2,3,…,n] contains a total of n! unique permutations.
By listing and labeling all of the permutations in order,
We get the following sequence (ie, for n = 3):
“123”
“132”
“213”
“231”
“312”
“321”
Given n and k, return the kth permutation sequence.
Note: Given n will be between 1 and 9 inclusive.
输出
返回一个字符串
样例输入
3 5 |
样例输出
”312” |
算法思想:
这道题刚开始,我是使用组合数学中的生成排列算法的方法来解决的,提交之后虽然通过,但是时间复杂度为O( knlg(n) ),速度很慢。然后重新换了一种思路来写,不是通过上一个字符串来生成当前字符串的,而是直接生成当前字符串。通过找规律。
比如说n = 4,你有{1,2,3,4}
如果你列出你所有的排列
1 +(排列2,3,4)
2 +(排列1,3,4)
3 +(排列1,2,4)
4 +(排列1,2,3)
我们知道如何计算n个数字的排列数为 n!,所以3个数字意味着有6个可能的排列,也就是说4个数有24中排列。所以如果你要寻找(k = 14)第14个排列,那将是在 3 +(排列1,2,4)子集中寻找。用编程实现,你取k = 13(由于下标从0开始),并将其除以我们从阶乘得到的6,商就是所需的数字的索引。在阵列{1,2,3,4}中,k /(n-1)! = 13 /(4-1)! = 13/3! = 13/6 = 2。数组{1,2,3,4在索引2处的值为3,所以第一个数字是3。
其他后续数字的生成以此类似,因为不能重复,故需将上次已选择删除,使得可选数字规模减小。
{1,2,4}的排列将是:
1 +(排列2,4)
2 +(排列1,4)
4 +(排列1,2)
但是我们的k不再是第14个,因为在上一步中,我们已经从1和2开始排除了1、2的4个数字排列。所以你从k中减去1、2的排列数,编程实现如下:
k = k - (从前一个的索引)(n-1)! = k-2 (n-1)! = 13 - 2 *(3)! = 1
在第二个步骤中,2个数字的排列只有2种可能性,意味着上面列出的三个排列中的每一个有两种可能性,总共为6。我们正在寻找第一个(因为此时k = 1),所以这将在1 + (排列2,4)子排列中寻找。
意味着:要获取数字的索引是k /(n - 2)! = 1 /(4-2)! = 1/2! = 0 ..从{1,2,4},索引0为1
所以我们到目前为止的数字是3,1 ,然后重复。
{2,4}
k = k - (从过去的索引)(n-2)! = k-0 (n-2)! = 1 - 0 = 1;
第三个数字的索引= k /(n-3)! = 1 /(4-3)! = 1/1! = 1 …从{2,4},索引1有4
第三个数是4
{2}
k = k - (从过去的索引)(n-3)! = k - 1 (4 - 3)! = 1 - 1 = 0;
第四个数字的索引= k /(n-4)! = 0 /(4-4)! = 0/1 = 0,从{2},索引0有2
第四个数字是2
故返回3142.
生成排列源代码
/*
Author:杨林峰
Date:2017.10.27
LeetCode(60):Permutation Sequence
*/
class Solution {
public:
string getPermutation(int n, int k) {
string str;
for (int i = 1; i <= n; i++)
{
str += i + '0';
}
int cnt = 1;
while (cnt < k)
{
int pos1, pos2;
//找出左边数字比右边小的,并记录其位置
for (int i = n - 2; i >= 0; i--)
{
if (str[i] < str[i + 1])
{
pos1 = i;
break;
}
}
int temp = str[pos1 + 1];
//找出pos1右边比temp大的数但是最接近temp的数,记录其位置
for (int i = pos1 + 1; i < n; i++)
{
if (str[i] > str[pos1] && str[i] <= temp)
{
temp = str[i];
pos2 = i;
}
}
//交换
swap(str[pos1], str[pos2]);
//对后面的数从小到大排序
sort(str.begin() + pos1 + 1, str.end());
//cout << str << endl;
cnt++;
}
cout << str << endl;
return str;
}
};
最优源代码
/*
Author:杨林峰
Date:2017.10.27
LeetCode(60):Permutation Sequence
*/
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
using namespace std;
int factorial[11];
string getPermutation(int n, int k)
{
int pos = 0;
int sum = 1;
factorial[0] = 1;
//生成阶乘数
for (int i = 1; i <= n; i++)
{
sum *= i;
factorial[i] = sum;
}
vector<int> vec, vec1; //vec为初始值,vec1为生成的结果值
//初始化vec
for (int i = 1; i <= n; i++)
{
vec.push_back(i);
}
k--;
//去生成结果的每一位数
for (int i = 1; i <= n; i++)
{
int index = k / factorial[n - i];
vec1.push_back(vec[index]);
vec.erase(vec.begin() + index);
k -= index*factorial[n - i];
}
//将数字转换成字符串
string str;
for (vector<int>::iterator it = vec1.begin(); it != vec1.end(); it++)
{
str += (*it) + '0';
}
return str;
}
int main()
{
int n, k;
while (cin >> n >> k)
{
string str = getPermutation(n, k);
cout << str << endl;
}
return 0;
}
算法复杂度:
由源代码可知,第一种算法时间复杂度为O(knlogn),因为大循环要循环k次,循环里复杂度最大的是排序操作,故时间复杂度为O(knlogn),第二种算法的时间复杂度为O(n^2),因为vector擦除元素时间复杂度为O(n),要循环n次来生成n个数,故时间复杂度为O(n^2)。