leetcode[60]Permutation Sequence 以及 全排列的编码与解码——康托展开 (附完整代码)
来源:互联网 发布:网络综艺节目2017 编辑:程序博客网 时间:2024/06/09 18:56
leetcode[60]Permutation Sequence C++的代码答案:
- class Solution {
public:
string getPermutation(int n, int k) {
assert(k > 0);
string result;
string source_s;
for(int i=1;i<=n;++i){
source_s.push_back('0'+i);
}//源数据,记录全排列每一步还需排列的数字
k--;//康托展开k-1在计算
for(int i =0;i<n;++i){
int temp;
temp = k/jieche(n-i-1);
result.push_back(source_s[temp]);
source_s.erase(temp,1);
k=k%jieche(n-i-1);
}
return result;
}
int jieche(int a){
int jc_res = 1;
for(int i =2;i<=a;i++){
jc_res = jc_res*i;
}
return jc_res;
}
};
一、康托展开:全排列到一个自然数的双射
以此类推,直至0*0!
三、全排列的解码
通过康托逆展开生成全排列
如果已知 s = ["A", "B", "C", "D"],X(s1) = 20,能否推出 s1 = ["D", "B", "A", "C"] 呢?
因为已知 X(s1) = a4*3! + a3*2! + a2*1! + a1*0! = 20,所以问题变成由 20 能否唯一地映射出一组 a4、a3、a2、a1?如果不考虑 ai 的取值范围,有
3*3! + 1*2! + 0*1! + 0*0! = 20
2*3! + 4*2! + 0*1! + 0*0! = 20
1*3! + 7*2! + 0*1! + 0*0! = 20
0*3! + 10*2! + 0*1! + 0*0! = 20
0*3! + 0*2! + 20*1! + 0*0! = 20
等等。但是满足 0 <= ai <= n-1 的只有第一组。可以使用辗转相除的方法得到 ai,如下图所示:
知道了a4、a3、a2、a1的值,就可以知道s1[0] 是子数组["A", "B", "C", "D"]中第3大的元素 "D",s1[1] 是子数组 ["A", "B", "C"] 中第1大的元素"B",s1[2] 是子数组 ["A", "C"] 中第0大的元素"A",s[3] 是子数组 ["C"] 中第0大的元素"C",所以s1 = ["D", "B", "A", "C"]。
这样我们就能写出一个函数 Permutation3(),它可以返回 s 的第 m 个排列。
// 返回 s 的第 m 个排列(m 从 0 开始)。要求 s 是升序排列的
static
string
[] Permutation3(
string
[] s,
int
m)
{
IList<
string
> sub =
new
List<
string
>(s);
IList<
string
> result =
new
List<
string
>();
for
(
int
i = s.Length - 1; i >= 0; i--)
{
int
f = Factorial(i);
int
ai = m / f;
string
item = sub[ai];
// 由于数组是升序排列的,所以第 ai 大的元素就是 sub[ai]
result.Add(item);
sub.RemoveAt(ai);
m = m % f;
}
return
result.ToArray();
}
测试一下:
string
[] s =
new
string
[] {
"A"
,
"B"
,
"C"
,
"D"
};
for
(
int
i = 0; i < Factorial(s.Length); i++)
{
string
[] s1 = Permutation3(s, i);
Console.WriteLine(s1.Montage(t => t,
" "
));
}
附录 康托展开是怎么来的?
很显然,康托展开是本文的关键所在。你说康托他老人家当初是怎么想出来这种展开的方法的呢?我们还是以 s=["A", "B", "C"] 为例:
A B C | 0
A C B | 1
B A C | 2
B C A | 3
C A B | 4
C B A | 5
他的思路可能是这样的:首先,确定一个目标:将每个排列映射为一个自然数,这个自然数是顺序增长的(或者至少要有一定的规律)。要说映射成自然数,第一个想到的方法自然是把数组的下标当作一个n进制的数字,但是正如本文开篇所讨论的,这个数字并没有什么规律;第二个方法是计数,也就是令 X = 当前排列之前有多少个排列。例如 A B C 是第一个排列,它前面没有任何排列,所以 X(ABC) = 0;A C B 前面有一个排列,所以 X(ACB) = 1……那么如何才能知道 X(BCA) = 3 也就是 B C A 的前面有3个排列呢?这里的技巧仍然是分解——把问题分隔成相互独立的有限的小块。具体的方法是:先求出 B 第一次出现在最高位(也就是 B A C 这个排列)时前面有几个排列,再求出 B C A 是 B A C 后面第几个排列,把这个两个数相加就是想要的结果了。
先看第一个问题:B 第一次出现在最高位(也就是 B A C 这个排列)时前面有几个排列?由于已知 B A C 前面的排列一定是 A 开头的,所以只有 A 后面的两个元素可以变化,所以排列数是 P(2,2) = 2! 个。
第二个问题:B C A 是 B A C 后面第几个排列?因为都是 B 开头的,所以可以把开头的 B 忽略,问题变成 C A 是 A C 后面的第几个排列?同样,可以先考虑 C 第一次出现在最高位时前面有几个排列,因为 C A 前面的排列肯定是 A 开头的,所以只有 A 后面的一个元素可以变化,所以排列数是 P(1,1) = 1! 个。
所以 X(BCA) = 2! + 1! = 3
再例如想求 X(CBA),同样是先考虑 C 第一次出现在最高位时前面有多少个排列,因为比 C 小的元素有 A 和 B 两个,所以是 2*2! 个。再求出 B A 是 A B 后面的第 1! 个排列。就可以知道 X(CBA) = 2*2! + 1! = 5 了
- leetcode[60]Permutation Sequence 以及 全排列的编码与解码——康托展开 (附完整代码)
- 全排列的编码与解码——康托展开 (附完整代码)
- 全排列的编码与解码——康托展开 (附完整代码)
- 全排列的编码与解码——康托展开 (附完整代码)
- 全排列的编码与解码——康托展开及其逆展开
- 全排列的编码与解码——【康托展开及其逆运算】
- 全排列的编码与解码——康托展开
- 全排列与 康托展开
- 康托展开与全排列
- UVA 11525 Permutation-不重复全排列的第n项-(康托展开)
- 康托展开 全排列
- 数据结构与算法[LeetCode]—Permutation Sequence 求n个数的全排列中第K个序列
- 康拓排列--全排列的解码与编码
- 全排列的本质——康托展开以及本质原理分析——选取第N个——由序列推知第几个
- vijos - P1092全排列 (康托展开 + 康托展开的逆运算)
- 康托展开(基于全排列的某一种hash)
- 关于全排列问题的总结(康托展开)
- 康托展开,用于全排列的hash
- java.lang.IllegalArgumentException: Property 'sqlSessionFactory'
- 界面切换,解决界面卡顿问题
- 剑指offer:数字在排序数组中出现的次数
- [牛客]双栈队列练习题
- TensorFlow iOS 怎样在IOS平台上跑TensorFlow人工智能学习系统
- leetcode[60]Permutation Sequence 以及 全排列的编码与解码——康托展开 (附完整代码)
- 欢迎使用CSDN-markdown编辑器
- UNIX再学习 -- exit 和 wait 系列函数
- Docker入门(3)
- Project build error: 'dependencies.dependency.version' for com.sun:tools:jar
- (42):慎用可变参数
- Log4j.properties配置详解
- 创建线程有几种不同的方式?
- 深入理解C# 静态类与非静态类、静态成员的区别