排列问题
Kth Permutation
有一组数例如”123456”, 它们可以组成
种排列, 将这些数从小到大排列:1
n!
1
2
3
4
"123456"
"123465"
"123546"
...
问题: 在这些排列中,如何求出第k大的数(k从0开始)?
思考: 考虑简单的情况 n = 3:
1
2
3
4
5
6
7
perm k k / (n-1)!
123 0 0
132 1 0
213 2 1
231 3 1
312 4 2
321 5 2
由于在n个元素组成的排列中, 以某个元素i开头的排列共有(n-1)!个. 不难得出结论:
- 第k大的排列以 perm[k/(n-1)!] 开头.
- 第k大的n个元素的排列, 等于以perm[k/(n-1)!]开头的元素, 上去掉这个元素后剩下的元素组成的第 k % (n-1)! 大的排列.
于是就有了一个很简单的递归关系:
1
kth_perm(perm, k) = perm[k/(n-1)!] + kth_perm(perm[0..i-1] + perm[i..n-1], k % (n-1)!)
根据这个关系可以写出kth_permutation的代码:
Permutation Rank
给定一个排列”231”, 它是”123”的排列中第几大的?
由于2是第1大的, 因此有
个首位比 2 小的.1
2! * 1
由于3是剩下的数中第1大的, 因此有
个第二位比3小的.1
1! * 1
因此比”231”小的 有
个, 因此”231”是第3大的. (均从0开始)1
2! * 1 + 1 ! * 1 = 3
一个比较naive的实现:
Next permutation
已知一个排列”146532”, 如何求出比它大的下一个排列? 这个问题被称为next_permutation.
一个简单的方法是, 使用前面的结论, 首先求出rank, 然后求第rank+1的排列. next_permutation(vec): k = rank(vec) return kth_permutation(vec, k+1)
不过也有一个更加subtle, 效率也更高的方法:
首先找出最大的i ,使得
, 例如
1 4 6 5 3 2
i
此时, a是所有以a[0..i]开头的排列中最大的. 也就是说,
1 4 6 5 3 2是所有 1 4 开头排列中最大的. (如果这一步找不到, 说明原序列倒序排列, 已经是最大的一个排列了.)1
a[i] < a[i+1]
现在我们要使a变大, 但是变大的幅度要尽可能小, 因此我们要找到一个刚好比a[i]大的数, 也就是比a[i]大的数中最小的一个.
因此,我们找出最大的j, 使得
:
1 4 6 5 3 2
i j1
a[i] < a[j]
a[j]就是比a[i]大的数中最小的一个. 我们把它和a[i]交换: 1 5 6 4 3 2 i j
此时, a是 a[0..i]开头的数中最大的一个, 这不满足我们的要求, 我们希望它是最小的一个. 因此, 我们将a[i+1..n-1]倒序: 1 5 2 3 4 6
即为所求. c++实现: