在解决排列问题时,一般有以下问题:
- 给你一个排列,问你是第几个排列
- 给你一个排列,问下一个排列是多少
- 给你一个排列,问前一个排列是多少
- 给你一个数i,问第i个排列是多少
一般暴力的话使用递归加回溯都能解决,但是时间复杂福比较高,递归多了栈也会溢出。
这里使用康拓展开的思想,具体思路。
看不懂没关系,继续看就知道啦。
康拓拓展
假如给你一个排列[2,4,3,1],问你是第几个排列。这时你可以想想,以1开头的所有排列你是不是可以手动算出来,就是3!,没错,看到这里我当时顿悟,后面的就不看了,直接上手写代码。当时就不知道有康拓思想,虽然代码编写的有点慢,卡在了一个问题上,但是最后还是通过了。继续:
- 注意count += 3! = 6;1位于第一位排列的所以可能就是数就是6,不用算后面的,然后就是2排在第一位了。
- 这时看第二位4,按照从小到大的顺序,第二位应该是1,3。所以要计算以1,3位于第二位的全排列数,第二位的全排列数是2!,也就是2*2!。即count += 2*2! = 10;这时按照顺序第二位不是1,3(这里有2位)就是4了。(这里2被忽略,因为被使用过了)
- 再看第三位数是3;按照从小到大的顺序,应该是1(只有一个了),但是现在是3,所以计算数字1位于第三位的全排列数,是1*1! = 1,所以count += 11;
- 再看第四位是1,最后一位了,必须是他了,排在他前面的都被使用完了,所以为0,最后一位是0!也就是0*0! = 0
- 最后由于是从0开始的,count= 11,也就是在他前面有11个排列,那么该排列就是第12个排列。
这就对应上面的公式,是对应上面1-5中乘法的前面,代表该数字在源排列[1,2,3,4]中第几个没有出现过的数字,由于下标是从0开始的,所以要减去1。即 = 当前数字2在源排列中没有出现过的第几个数 - 1;
代码如下:
//对前 10 个自然数(0 ~ 9)的阶乘存入表
//以免去对其额外的计算
const int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
/**
* @brief 康拓展开
*
* @param[in] permutation 输入的一个全排列
* @param[out] num 输入的康拓映射,即是第几个全排列
*/
int contor(const vector<int>& permutation) {
int num = 0;
int len = permutation.size();
for (int i = 0; i < len; ++i) {
int cnt = 0; // 在 i 之后,比 i 还小的有几个
for (int j = i + 1; j < len; ++j)
if (permutation[i] > permutation[j]) ++cnt;
num += cnt * fact[len - i - 1];
}
return num + 1;
}
康拓逆排序
这个逆排序是求解给定一个数rank,获得第rank个排序的结果。这里另rank = 10;
首先rank - 1,因为求解是从0开始开始的。
, 比如9/3! = 1...3,说明取在原始排列[1,2,3,4]中第一个没有取到的数是不行的,那就找第二个,就是2
然后rank = rank%(n-1)!,即 rank= 3
重复上一步,3/2! = 1...1,同样说明位于第2位的数在原始排列[1,2,3,4]中取第一个未取到的是不行的,那就是第二个3,因为2被取过了。然后rank = rank%(n-1)! = 1
再重复,1/1! = 1...0,说明位于第3位的数在原始排列中取第一个未取到的是不行的,那就第二个4,。。。后面就没了。
代码如下:
//对前 10 个自然数(0 ~ 9)的阶乘存入表
//以免去对其额外的计算
const int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
/**
* @brief 逆康拓展开
*
* @param[in] bits 给定全排列的使用数字个数
* @param[in] num 给定全排列的次位
* @param[out] permutation 输出对应的全排列
*/
vector<int> revContor(int bits, int num) {
num = num - 1; //有 num - 1 个排列比目标序列要小
vector<bool> vis(bits + 1, false);
vector<int> permutation(bits, -1);
int n, residue = num;
for (int i = 0; i < bits; ++i) {
n = residue / (fact[bits - i - 1]);
residue = residue % (fact[bits - i - 1]);
for (int j = 1; j <= bits; ++j) {
if (!vis[j] && !(n--)) {
vis[j] = true;
permutation[i] = j;
break;
}
}
}
return permutation;
}