排列序号II - Lintcode198
已知:
给出一个可能包含重复数字的排列,求这些数字的所有排列按字典序排序后该排列在其中的编号。编号从1开始。
案例:
给出排列[1, 4, 2, 2],其编号为3
思路:
我们用递归的方法,先求出以当前第一个数为基准,必然按字典顺序排序在当前数组之前的数量,然后求以第二个数为基准,以此类推直到最后一个数。
具体解法:(以1,4,2,3为例)
- 1,4,2,3的时候,1是所有数字中值最小的,因此绝对在它之前的数组个数为0,递归调用函数判断4,2,3的值
- 4,2,3的时候,绝对在其之前的是第二个位置替换为2,3的情况下得到的数组(替换成2则后续两位为3,4共两种可能,替换为3同理两种可能,共四种可能,进入下一层循环)
- 2,3的时候,2是当前最小,值为0
- 3是最后一位,直接返回0
因为是1为底数的,所以结果要+1,结果为5
上述4,2,3的情况下,好的解法是得到一个二维数组显示当前数组中的所有唯一数和这个唯一数在数组中的个数。根据“从3个数中挑1个,然后从剩下两个数中挑1个”这样的解题思路,直接求得总共的可能性。
综上,代码如下:
import java.math.BigInteger;
import java.util.TreeSet;
public class Solution {
public long permutationIndexII(int[] A) {
return calculate(A) + 1;
}
private long calculate(int[] a) {
if (a.length <= 1) {
return 0;
}
int[] temp = new int[a.length - 1];
for (int i = 0; i < temp.length; i++) {
temp[i] = a[i + 1];
}
if (zeroTheMinest(a)) {
return calculate(temp);
} else {
return doWork(a, a[0]) + calculate(temp);
}
}
private long doWork(int[] num, int value) {
long ret = 0;
int[][] valueAndCount = getValueAndCount(num);
for (int i = 0; i < valueAndCount.length; i++) {
//比如说二维数组表示一个1,两个2,三个3,当前数字为3,
//则获得第一位为1或者2的所有可能性
if (valueAndCount[i][0] >= value) {
break;
}
ret += getCount(valueAndCount, i, num.length - 1);
}
return ret;
}
//base其实就是二维数组第二维的数字之和 - 1,避免重复运算
//index是所有值小于当前数组第一位的值在二维数组中的index
private long getCount(int[][] valueAndCount, int index, int base) {
valueAndCount[index][1]--;
long ret = 1;
for (int i = 0; i < valueAndCount.length; i++) {
ret *= C(base, valueAndCount[i][1]);
base -= valueAndCount[i][1];
}
//这里可以克隆一个二维数组,如果不克隆,必须把二维数组变回原样
//因为一些机制,当我们在函数中操作数组,数组本身内容也会改变
valueAndCount[index][1]++;
return ret;
}
//从base个数据中挑选num个数据的运算符
private long C(int base, int num) {
if (base - num < num) {
num = base - num;
}
//因为数字可能很大,如果直接乘会超越Long的范围,故取巧
BigInteger b = BigInteger.ONE;
for (int i = 0; i < num; i++) {
b = b.multiply(BigInteger.valueOf(base - i));
}
for (int i = 0; i < num; i++) {
b = b.divide(BigInteger.valueOf(num - i));
}
return b.longValue();
}
//获取一个数组的重复数组的值以及重复的次数,形成一个二维数组
private int[][] getValueAndCount(int[] num) {
TreeSet<Integer> dividualNum = new TreeSet<Integer>();
for (int i : num) {
dividualNum.add(i);
}
int[][] valueAndCount = new int[dividualNum.size()][2];
for (int i = 0; i < valueAndCount.length; i++) {
valueAndCount[i][0] = dividualNum.pollFirst();
int temp = 0;
for (int j = 0; j < num.length; j++) {
if (num[j] == valueAndCount[i][0]) {
temp++;
}
}
valueAndCount[i][1] = temp;
}
return valueAndCount;
}
//如果第一个数是当前数组最小值,则直接进入下一层循环
private boolean zeroTheMinest(int[] a) {
for (int i = 1; i < a.length; i++) {
if (a[i] < a[0]) {
return false;
}
}
return true;
}
}
希望对您有所帮助,o( ̄︶ ̄)o