web-dev-qa-db-ja.com

nとkを指定すると、k番目の順列シーケンスを返します

セット[1,2,3、…、n]には合計nが含まれています!ユニークな順列。

すべての順列を順番にリストしてラベルを付けると、次のシーケンスが得られます(つまり、n = 3の場合)。

  1. 「123」
  2. 「132」
  3. 「213」
  4. 「231」
  5. 「312」
  6. "321" nとkを指定すると、k番目の順列シーケンスを返します。

たとえば、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);
    }
}
18
explorer

したがって、質問を正しく読んでいる場合、kがBigIntegerを必要とするほど大きくない場合、できればBigIntegerを使用せずにk番目の順列を見つけたいと思います。

シーケンスを見れば

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

たとえば、「2、0、0」は「1、2、3」というリストから始まり、3番目の値(ゼロからインデックスを作成しているため)、つまり3を取り、残りの数字の最初の値を取ります」 1、2 "は1で、残りの桁の最初の数字は" 2 "です。したがって、「3、1、2」が生成されます。

これらのインデックスを生成するには、右から左に移動し、kを1で割ります!右端の2か所の場合は2です。次に3!次に4!など、次に、その位置で可能なインデックスの数で結果をモジュロ化します。これは、右端が1、右から2番目が2です。実行中の積を維持できるため、毎回階乗を計算する必要はありません。 。

階乗で除算されたkがゼロになるとすぐにループから抜け出すことができるため、kを階乗で除算した最後の場所を乗じたおおよそのkのサイズまでの階乗を計算するだけで済みます。 kが大きすぎる場合は、BigIntegersに切り替える必要があります。

インデックスを取得したら、それらを使用して置換を生成するのは非常に簡単です。

コード(kは0から始まるため、最初のパスを見つけるために、1ではなく0):

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));
}

デモ

出力:

[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]

n = 100の10000000番目の順列:

[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 ]

31
samgak

もちろん、そのようなインターフェースを持つbigintsが必要です

_n = 100_がある場合、_n!_順列があり、kが_k=<1,n!>_の範囲にあることを意味します

_100!=93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
_

これは標準_unsigned int_に適合しません

_2^32=          4294967296
2^64=18446744073709551616
_

参照 高速で正確なbigint階乗

インターフェイスを少し変更すると、突然bigintsが不要になります

[〜#〜] api [〜#〜]を変更するだけで、kしたがって、次のようなものが必要です。

もちろん、これは、順列の使用が連続的である場合にのみ使用できます。また、関数previous()を作成して、ほぼシーケンシャルなアルゴリズムを処理することもできます。ランダムまたは非順次アクセスの場合は、bigintsを使用する必要があります

1
Spektre

k番目の順列(この質問への回答で使用)のインデックスは factoradickの表現であり、階乗または実行積を使用せずに計算できます。

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を埋め込む必要があります。または、右端から順列を適用することもできます。

1