Комбинаторика с повторными символами на начальной группе - PullRequest
1 голос
/ 27 августа 2009

Я знаю, что если у меня есть следующая группа чисел

{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

У меня может быть 5040 различных 4-значных номеров, используя 10! / (10 - 4)!

Но если я повторю одно число в начальной группе, как

{ 1, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

Сколько разных 4-значных чисел мы можем построить? Я знаю, что ответ 3360, просто не знаю, как это реализовать.

Внимание! В этом случае допустимыми должны быть числа типа «1123» или «1213», но не «1113», так как в исходной группе только две. Также для группы

{ 1, 1, 2, 2, 3, 4, 5, 6, 7, 8 }

Должно быть 2190 4-значных чисел. Любые идеи о том, как рассчитать эти ответы?

Это не домашнее задание, я получу эти цифры от определенного оборудования и, в зависимости от количества комбинаций, верну некоторые данные.

Ответы [ 5 ]

4 голосов
/ 28 августа 2009
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

Здесь вы выбираете четыре числа из десяти, и они могут быть любого порядка. Таким образом, решение

(10 choose 4) * 4! = 5040.

Теперь рассмотрим случай

{ 1, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

Здесь есть несколько комбинаций. Мы можем иметь ноль 1 с, один 1 или два 1. В первом случае есть

(8 choose 4) * 4!

возможных комбинаций. Во втором случае

(8 choose 3) * 4!

возможные комбинации. В третьем случае

(8 choose 2) * 4! / 2!

возможные комбинации. Этот последний требует объяснения. Есть восемь возможных не 1 цифр на выбор, и мы выбираем две. Две оставшиеся цифры равны 1 (предположим, что мы находимся в том случае, если наша 4-строка содержит две 1). Мы можем поместить эти четыре цифры в любом порядке, однако 1 являются взаимозаменяемыми, поэтому мы делим на количество возможных порядков 1 (2!).

Таким образом, ответ

(8 choose 4) * 4! + (8 choose 3) * 4! + (8 choose 2) * 4! / 2! = 3360.

Для случая

{ 1, 1, 2, 2, 3, 4, 5, 6, 7, 8 }

снова несколько комбинаций. Мы можем иметь ноль 1 и ноль 2, один 1 и ноль 2, два 1 и ноль 2, два 1 и один 2, два 1 и два 2, ноль 1 и один 2, ноль 1 и два 2, один 1 и два 2 , и один 1 и один 2. Они могут быть разработаны, как указано выше, и окончательный ответ будет

(6 choose 4) * 4!
    + 2 * (6 choose 3) * 4!
    + 2 * (6 choose 2) * 4! / 2!
    + (6 choose 2) * 4!
    + 2 * (6 choose 1) * 4! / 2!
    + (6 choose 0) * 4! / (2! * 2!)
= 2190.

Это довольно упрощенный подход к задачам такого рода. Есть и другие (например, включение / исключение ), которые являются более сложными, но текущий подход легче всего понять, если вы впервые сталкиваетесь с проблемами подобного рода.

0 голосов
/ 30 августа 2009

Если вам нужно сделать это на любом наборе, превышающем ваш пример, следите за переполнением в ваших промежуточных вычислениях. 13! уже достаточно велик, чтобы переполнить 32-битный UINT. Только несколько больше, чем это, переполнит 64 бита. Использование float / double не лучше, так как вы получите неправильный ответ без полной точности.

Вам потребуется использовать целочисленный класс произвольной точности или некоторый пользовательский числовой класс, который содержит полный список факторов при вычислении факториала или любых умножений, а затем выполнить исключение коэффициентов для упрощения деления.

0 голосов
/ 27 августа 2009

Это было бы довольно легко без дублирования. , , для

{ 1, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

Поскольку у вас есть только 9 различных вариантов (в отличие от 10 в исходной задаче), ответ должен быть 9! / (9 - 4)!

(Кстати, у вас может быть больше разных 4-значных чисел, если вы разрешите повторение, т. Е. 4456. Тогда ответом будет просто 9 ^ 4 4-значных чисел.)

Аналогично, {1, 1, 2, 2, 3, 4, 5, 6, 7, 8} имеет 8 различных вариантов, поэтому ответ должен быть 8! / (8 - 4)! по вашей оригинальной математике.

Правка и фактический ответ : Может быть, вы имели в виду, что если в вашем наборе дублируется 1, вы можете использовать две 1 в ответе?

Редактировать 2: Работающий, протестированный модуль Python предоставляется

В этом случае вы можете попытаться вычислить различное количество возможностей, а затем добавить результаты с дубликатами, как предполагает следующий код Python):

import math

def set_exclude(a, b):
    result = []
    for i in a:
        if not i in b:
            result.append(i)
    return result

def cnt_unique(aset, choices_picked, count_left, count_total):
    # Sanity check
    if count_left < 0:
        return 0
    if count_left == 0:
        return math.factorial(count_total)
    # Do non-duplicate combinations first
    # Set unprocessed = (set without any elements in choices_picked)
    unprocessed = set_exclude(aset, choices_picked)
    temp = len(set(unprocessed))

    # If we have no more valid items to choose from, this is impossible
    if temp >= count_left:
        result = math.factorial(temp) / math.factorial(temp - count_left) / \
                 math.factorial(count_left) * math.factorial(count_total)
    else:
        result = 0

    # Now find duplicate-involving combinations
    for member in set(unprocessed):
        valid = True
        for contest in choices_picked:
            if contest >= member:
                valid = False
                break

        if valid:
            count = unprocessed.count(member)
            new_choices = choices_picked + [ member ]
            for i in range(2, count+1):
                result_temp = cnt_unique(aset, new_choices, \
                  count_left - i, count_total)
                if result_temp != 0:
                    result_temp //= math.factorial(i)
                    result += result_temp
    return result

aset = [ 1, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
result_size = 4
combinations = cnt_unique(aset, [], result_size, result_size)

ОК, я проверял от руки, что алгоритм работает для всех ваших случаев, представленных выше. Я вполне уверен, что это работает в более общих случаях, но у меня нет времени на выполнение каких-либо дополнительных тестовых случаев (например, если было 3 1 или 3 группы дубликатов). Обратите внимание, что это также взорвалось бы, если бы в наборе не было чисел, которых не было бы в choices_picked (то есть у вас есть одно уникальное число, дублированное 10 раз).

Редактировать 3: Что касается количества вызовов функций с помощью этого алгоритма для больших наборов, я проверил следующий вызов функции, увеличивая переменную один раз для каждого нетривиального (count_left> = 0) вызова cnt_unique:

>>> def test():
        b = [0]
        c = time.time()
        result = cnt_unique(range(1,51) + range(1,51), [], 4, 4, b)
        c = time.time() - c
        print("Result: " + str(result))
        print("Time: " + str(c))
        print("Calls: " + str(b[0]))

>>> test()
Result: 6240150
Time: 0.0150001049042
Calls: 1276

Таким образом, для набора из 100 элементов с 2 записями для каждого номера 1-50 было 1276 вызовов. И это выполняется довольно быстро; один тик с time.time () равен 15 мс, поэтому он выполняется менее чем за 15 мс.

0 голосов
/ 28 августа 2009

Вам нужно решить проблему в 2 случаях:

Случай 1: ноль или один «1» в вашем 4-значном номере Количество перестановок составляет 9! / (9-4)! = 3024

Случай 2: два «1» в вашем 4-значном номере Вы знаете, что две цифры должны быть 1, поэтому есть 8 * 7 способов выбрать оставшиеся две цифры. И есть (4! / 2!) Способы расставить две «1» и две другие цифры. Следовательно, число перестановок составляет 8 * 7 * (4! / 2!) = 672

Похоже, ответ 3024 + 672 = 3696

0 голосов
/ 27 августа 2009

Возможно, вы захотите сослаться на эту выдающуюся реализацию комбинаторики .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...