Для заданных 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);
    }
}

person explorer    schedule 04.07.2015    source источник
comment
Чтобы обойти проблему с большими числами, вы, вероятно, захотите BigInteger или что-то в этом роде; Я не уверен, что ваш код правильный. Не могли бы вы объяснить, почему вы используете fact*n? А что такое база 0?   -  person G. Bach    schedule 04.07.2015
comment
@ G.Bach, чтобы изменить k на индекс? код правильный ... вы можете проверить его для данного примера в своем ide. ....   -  person explorer    schedule 04.07.2015
comment
@ G.Bach факт * n представляет n! .... в этом случае, если k = 7, что больше n! = 6, тогда ваше k должно быть 1 (7-6) .... подумайте, что произойдет, когда k ›n!   -  person explorer    schedule 04.07.2015
comment
Ах да, вы в основном идете k := k mod n!, я вижу. Вы пробовали использовать BigInteger? При ближайшем рассмотрении ваш код кажется мне правильным, за исключением того, что он не проверяет, являются ли n или k неположительными, k больше 2 * n !, и целочисленное переполнение.   -  person G. Bach    schedule 04.07.2015
comment
@ G.Bach Я знаю о BigInteger ... но нет необходимости использовать BigInteger ... причина 1. с этим было бы трудно работать с другими переменными и 2. возможно, существует решение / лучший алгоритм! вот что я пытаюсь придумать   -  person explorer    schedule 04.07.2015
comment
Этот вопрос не помог? (Первый Q в связанном списке справа) stackoverflow.com/questions/1506078/   -  person rici    schedule 04.07.2015
comment
@rici nop, это не так ..   -  person explorer    schedule 04.07.2015
comment
Может быть, есть способ сопоставить k с перестановкой, не проходя через n! записи. Это похоже на то, как вы можете добавить 0-n, не проходя через все n элементов.   -  person David Ehrmann    schedule 04.07.2015
comment
Как вы планируете внести вклад в программу? Число перестановок охватывает диапазон от 1 до N !, вы не сможете выбрать перестановку, если не разрешите использовать числа, достаточно большие, чтобы сохранить факториальное значение!   -  person CiaPan    schedule 05.07.2015


Ответы (4)


Итак, если я правильно читаю вопрос, вы хотите найти k-ю перестановку, желательно без использования BigInteger, при условии, что 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

Так, например, «2, 0, 0» означает начало со списка «1, 2, 3», затем возьмите третью (потому что мы индексируем с нуля), то есть 3, затем возьмите первую из оставшихся цифр » 1, 2 ", то есть 1, затем первая из оставшихся цифр, то есть" 2 ". Таким образом, получается «3, 1, 2».

Чтобы сгенерировать эти индексы, идите справа налево и разделите 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));
}

Демо

вывод:

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

10000000-я перестановка для n = 100:

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

person samgak    schedule 04.07.2015
comment
Спасибо @samgak .... эффективный алгоритм с лучшим объяснением и простым кодом ... Большое спасибо! - person explorer; 04.07.2015
comment
Я не совсем могу осмыслить одну вещь. Как индексы [n-place] = (k / divisor)% place работают правильно ?? - person Shubham Kadlag; 16.05.2019
comment
@ShubhamKadlag переменная divisor содержит факториал (сначала 1, затем 1, затем 2, затем 6 и т. Д.), Поэтому она многократно умножается на place. Разделив k на делитель, взяв по модулю, мы получим индекс для каждой позиции. place хранит количество возможных значений индекса в каждой позиции, поэтому оно используется для вычисления по модулю. Индекс хранится в location[n-place] (а не в location[n]), потому что алгоритм работает справа налево. - person samgak; 16.05.2019
comment
Верно, но как один и тот же K работает для всех позиций? Я понимаю, почему это будет работать для первой цифры, но не для других цифр, поскольку k - это номер перестановки, поэтому не должно ли оно измениться с позицией как остаток после вычисления первой цифры ?. - person Shubham Kadlag; 17.05.2019
comment
Подумайте о простом случае, когда вы находите цифры десятичного числа, например. 4836. Вы можете вычислить крайнее правое место, разделив 4836 на 1, затем по модулю 10, чтобы получить 6. Затем 4836 на 10 по модулю 10, чтобы получить 3, затем 4836 на 100 по модулю 10, чтобы получить 8 и т. Д. Нам не нужно менять число 4836 каждый раз. Случай перестановки аналогичен, но мы используем факториалы вместо степеней 10 и изменения значений модуля. - person samgak; 18.05.2019
comment
Созданные индексы - это просто factoradic представление k. - person Quirk; 20.06.2020

Индексы для k-й перестановки (использованные в ответе на этот вопрос) - это factoradic представление k и может быть вычислен без использования факториала или текущего произведения.

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 слева, чтобы длина массива индексов была равна количеству элементов для получения фактических индексов. В качестве альтернативы перестановка может быть применена с правого конца.

person Artem Konovalenkov    schedule 28.09.2018

Конечно, bigints с таким интерфейсом нужен

когда у вас есть n = 100, у вас есть n! перестановки, что означает, что k находится в диапазоне k=<1,n!>

100!=93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

что не вписывается в стандарт unsigned int

2^32=          4294967296
2^64=18446744073709551616

см. Быстрый точный факториал bigint

если вы немного измените интерфейс, вам вдруг больше не понадобятся bigint

просто измените API, чтобы он последовательно возвращал 1-ю, 2-ю, 3-ю, ... перестановку без указания k, поэтому вам понадобится что-то вроде:

конечно, это можно использовать, только если вы используете перестановку также последовательно. Вы также можете сделать функцию previous() для обработки почти последовательных алгоритмов. Для произвольного или непоследовательного доступа вам необходимо использовать bigints

person Spektre    schedule 04.07.2015

Сначала мы можем создать фактологическое представление 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();
}
person Sandhya Giri    schedule 21.06.2020