排列问题
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的代码:
vector<int> kth_permutation(const vector<int> &vec, int k)
{
if (k == 0)
return vec;
int n = vec.size();
k %= fact(n);
int t = fact(n-1);
int i = k / t;
int kk = k % t;
vector<int> result;
result.push_back(vec[i]);
vector<int> left;
copy(vec.begin(), vec.begin()+i, back_inserter(left));
copy(vec.begin()+i+1, vec.end(), back_inserter(left));
left = kth_permutation(left, kk);
copy(left.begin(), left.end(), back_inserter(result));
return result;
}
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的实现:
int permutation_rank(vector<int> &vec)
{
vector<int> vec2(vec.begin(), vec.end());
sort(vec2.begin(), vec2.end());
map<int, int> ranks;
for (int i = 0; i < vec2.size(); i++) {
ranks[vec2[i]] = i;
}
int rank = 0;
int n = vec.size();
for (int i = 0; i < vec.size(); i++) {
rank += fact(n-1) * ranks[vec[i]];
for (map<int, int>::iterator it = ranks.begin(); it != ranks.end(); ++it) {
if (it->first > vec[i])
it->second--;
}
n--;
}
return rank;
}
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++实现:
void reverse(vector<int> &num, int i) {
for(int j = num.size() - 1; j > i; j--) {
swap(num[i], num[j]);
i++;
}
}
void nextPermutation(vector<int> &num) {
if (num.size() <= 1) {
return;
}
// find max i where num[i] < num[i+1]
int i;
for (i = num.size()-2; i >= 0 && num[i] >= num[i+1]; i--);
if (num[i] >= num[i+1]) {
reverse(num, 0);
return;
}
// find max j where num[i] < num[j]
int j;
for (j = num.size()-1; num[i] >= num[j]; j--);
swap(num[i], num[j]);
reverse(num, i+1);
}