題目鏈接:
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)。