给定n和k,返回第k个排列序列

时间:2015-07-04 01:49:18

标签: java algorithm data-structures permutation backtracking

集合[1,2,3,...,n]共包含n!独特的排列。

按顺序列出并标记所有排列, 我们得到以下序列(即n = 3):

  1. " 123"
  2. " 132"
  3. " 213"
  4. " 231"
  5. " 312"
  6. " 321" 给定n和k,返回第k个排列序列。
  7. 例如,给定n = 3,k = 4,ans =" 231"。

    那里有多种解决方案。但是它们都使用阶乘或者复杂度大于O(n),例如O(n!)。如果你使用阶乘并在位置找到k /(n-1)!的数字,那么当n很大(n = 100)时会出现问题。这里n很大,(n-1)!溢出并变为0.结果,我得到一个零除错误...任何解决方案或算法?

    这是我的代码:

    public class KthPermutation {
        public String getPermutation(int n, int k) {
            // initialize all numbers
            ArrayList<Integer> numberList = new ArrayList<Integer>();
    
            for (int i = 1; i <= n; i++) {
                numberList.add(i);
            }
            int fact = 1;   // set factorial of n-1
    
            for (int i = 1; i <= n-1; i++) {
                fact = fact * i;
            }   
    
            if ((long) k > (long) fact * n) {
                k = (int) ((long) k - (long) (fact * n));
            }
            k--; // set k to base 0
    
            StringBuilder result = new StringBuilder();
            result = getP(result, numberList, n, k, fact);
            return result.toString();
        }
        public static StringBuilder getP(StringBuilder result,
                    ArrayList<Integer> numberList, int n, int k, int fact) {    
            if (numberList.size() == 1 || n == 1) {
                result.append(numberList.get(0));
                return result;  // return condition
            }
            int number = (k / fact) + 1 ;
            result.append(numberList.get(number - 1));
            numberList.remove(number - 1);
            k = k % fact;  // update k
            fact = fact / (n - 1);
            n--;
            return getP(result, numberList, n, k, fact);
        }
    }
    

4 个答案:

答案 0 :(得分:22)

因此,如果我正确地阅读了这个问题,你想找到第k个排列,最好不要使用BigIntegers,只要k不够大就不需要BigInteger。

如果我们看一下序列

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

我们可以重写它,以便每个位置的数字是到目前为止尚未出现的数字列表的索引:

0 0 0
0 1 0
1 0 0
1 1 0
2 0 0
2 1 0

所以例如&#34; 2,0,0和#34;意味着从列表&#34; 1,2,3&#34;开始,然后取第三个(因为我们从零索引),这是一个3,然后取第一个剩下的数字&#34; 1, 2&#34;这是1,然后是剩余数字的第一个,即&#34; 2&#34;。所以它产生&#34; 3,1,2&#34;。

要生成这些索引,请从右向左移动并将k除以1!对于最右边的两个地方,然后是2个!然后3!然后4!等等,然后用该位置的可能索引数量对结果进行模数化,最右边是1,第二右边是2等。你不必每次计算阶乘,因为你可以保持一个正在运行的产品。

一旦k除以阶乘为零,你就可以摆脱循环,所以你只需要计算阶乘,直到大约k的大小乘以k除以阶乘的最后一个位置为非零。如果k太大,则需要切换到BigIntegers。

获得索引后,使用它们生成排列非常简单。

代码(k从0开始,所以要找到第一个0,而不是1):

static public void findPermutation(int n, int k)
{
    int[] numbers = new int[n];
    int[] indices = new int[n];

    // initialise the numbers 1, 2, 3...
    for (int i = 0; i < n; i++)
        numbers[i] = i + 1;

    int divisor = 1;
    for (int place = 1; place <= n; place++)
    {
        if((k / divisor) == 0)
            break;  // all the remaining indices will be zero

        // compute the index at that place:
        indices[n-place] = (k / divisor) % place;
        divisor *= place;
    }

    // print out the indices:
    // System.out.println(Arrays.toString(indices));

    // permute the numbers array according to the indices:
    for (int i = 0; i < n; i++)
    {
        int index = indices[i] + i;

        // take the element at index and place it at i, moving the rest up
        if(index != i)
        {
            int temp = numbers[index];
            for(int j = index; j > i; j--)
               numbers[j] = numbers[j-1];
            numbers[i] = temp;
        }
    }

    // print out the permutation:
    System.out.println(Arrays.toString(numbers));
}

Demo

输出:

[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
对于n = 100,

10000000th置换:

[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23, 24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, 49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73, 74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,92,98,96,90,91,100,94,97,95, 99,93]

答案 1 :(得分:1)

当然需要bigints这样的界面

当您有n = 100时,您有n!个排列,这意味着k位于k=<1,n!>

范围内
100!=93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

不符合标准unsigned int

2^32=          4294967296
2^64=18446744073709551616

请参阅Fast exact bigint factorial

如果您稍微改变界面,则突然不再需要任何bigint

只需更改 API ,即可顺序返回第1,第2,第3,...排列,而无需指定k,因此您需要以下内容:

当然,只有在排列的使用也是连续的时候,这才可用。您还可以使函数previous()处理几乎是连续的算法。对于随机或非顺序访问,您需要使用bigint s

答案 2 :(得分:1)

首先,我们可以生成k的阶乘表示,然后使用它生成必要的排列。有关更多详细信息,请参见https://en.wikipedia.org/wiki/Factorial_number_system

public String getPermutation(int n, int k) {
    LinkedList<Integer> factoradic = new LinkedList<>();
    k=k-1; // because factoradic representation and its mapping to permutation starts from 0
    for(int i=1;i<=n; i++){ // get radix digits for n digits
        factoradic.addFirst(k%i);
        k=k/i;
    }
    
    //System.out.println(factoradic.size());
    List<Integer> numbers = new LinkedList<>();
    for(int i=1;i<=n;i++){
        numbers.add(i);
    }
    StringBuilder str = new StringBuilder();
    for(int x: factoradic){
        // System.out.println(x);
        str.append(String.valueOf(numbers.get(x)));
        numbers.remove(x);
    }
    return str.toString();
}

答案 3 :(得分:0)

k个排列的索引(用于此问题的答案)是k的{​​{3}}表示形式,可以在不使用阶乘或乘积的情况下进行计算。

public static List<Integer> toFactoradic(int x) {
    List<Integer> result = new ArrayList<>();

    for(int i = 1; x > 0; x /= i++) {
        result.add(x % i);
    }

    Collections.reverse(result);
    return result;
}

当然,索引数组应该从左侧填充0,以便索引数组的长度等于要获取实际索引的元素数。或者,可以从右端开始应用排列。