Нахождение кратчайших комбинаций в массиве / последовательности, равной сумме - PullRequest
6 голосов
/ 01 апреля 2012

Я полностью застрял и понятия не имею, как решить эту проблему.Допустим, у меня есть массив

arr = [1, 4, 5, 10]

и число

n = 8

Мне нужна кратчайшая последовательность изнутри arr, равная n.Так, например, следующие последовательности внутри arr равны n

c1 = 5,1,1,1
c2 = 4,4
c3= 1,1,1,1,1,1,1,1

Так что в вышеприведенном случае наш ответ - c2, потому что это самые короткие последовательности в arr, равные сумме.

Я не уверен, каков самый простой способ найти решение выше?Будем благодарны за любые идеи или помощь.

Спасибо!

Отредактировано:

  • Исправлен массив
  • Массив может иметь положительные значениятолько.
  • Я не уверен, как проблема с подмножеством решает эту проблему, возможно, из-за моего собственного невежества.Всегда ли алгоритм подмножества дает кратчайшую последовательность, равную сумме?Например, проблема подмножества идентифицирует c2 как ответ в вышеупомянутом сценарии?

Ответы [ 5 ]

3 голосов
/ 01 апреля 2012

В интересах людей, которые найдут этот вопрос в будущем -

Как отмечают Оскар Лопес и Приянк Бхатнагар, это проблема смены монет (сдачи, внесения изменений).

В general предложенное ими решение динамического программирования является оптимальным решением - как с точки зрения (доказуемо!) Всегда получения требуемой суммы с использованием наименьшего количества элементов, так и с точки зрения скорости выполнения.Если ваши базисные числа являются произвольными, то используйте решение динамического программирования.

Если ваши базисные числа "хорошие", однако, подойдет более простой алгоритм жадный .

Например, австралийская валютная система использует номиналы $100, $50, $20, $10, $5, $2, $1, $0.50, $0.20, $0.10, $0.05.Оптимальное изменение может быть дано для любой суммы, многократно задавая наибольшую возможную единицу изменения, пока оставшаяся сумма не станет равной нулю (или менее пяти центов).

Вот поучительная реализация алгоритма жадности, иллюстрирующая концепцию.

def greedy_give_change (denominations, amount):        
    # Sort from largest to smallest
    denominations = sorted(denominations, reverse=True)

    # number of each note/coin given
    change_given = list()

    for d in denominations:
        while amount > d:
            change_given.append(d)
            amount -= d

    return change_given

australian_coins = [100, 50, 20, 10, 5, 2, 1, 0.50, 0.20, 0.10, 0.05]
change = greedy_give_change(australian_coins, 313.37)
print (change)           # [100, 100, 100, 10, 2, 1, 0.2, 0.1, 0.05]
print (sum(change))      # 313.35

Для конкретного примера в исходном посте (denominations = [1, 4, 5, 10] и amount = 8) жадное решение не оптимально - оно даст [5, 1, 1, 1].Но жадное решение намного быстрее и проще, чем решение для динамического программирования, поэтому, если вы можете использовать его, вам следует!

2 голосов
/ 01 апреля 2012

Эта проблема известна как проблема минимальной замены монет.

Вы можете решить эту проблему с помощью динамического программирования. Вот псевдокод:

Set MinCoin[i] equal to Infinity for all of i
MinCoin[0] = 0

For i = 1 to N // The number N
For j = 0 to M - 1 // M denominations given
// Number i is broken into i-Value[j] for which we already know the answer
// And we update if it gives us lesser value than previous known.
   If (Value[j] <= i and MinCoin[i-Value[j]]+1 < MinCoin[i])
       MinCoin[i] = MinCoin[i-Value[j]]+1

Output MinCoin[N]
2 голосов
/ 01 апреля 2012

Как указывалось ранее, это проблема минимального изменения монет , обычно решаемая с помощью динамического программирования. Вот реализация Python, решаемая по сложности времени O (nC) и сложности пространства O (C), где n - это количество монет, а C - необходимое количество денег:

def min_change(V, C):
    table, solution = min_change_table(V, C)
    num_coins, coins = table[-1], []
    if num_coins == float('inf'):
        return []
    while C > 0:
        coins.append(V[solution[C]])
        C -= V[solution[C]]
    return coins

def min_change_table(V, C):
    m, n = C+1, len(V)
    table, solution = [0] * m, [0] * m
    for i in xrange(1, m):
        minNum, minIdx = float('inf'), -1
        for j in xrange(n):
            if V[j] <= i and 1 + table[i - V[j]] < minNum:
                minNum = 1 + table[i - V[j]]
                minIdx = j
        table[i] = minNum
        solution[i] = minIdx
    return (table, solution)

В вышеприведенных функциях V - список возможных монет и C требуемая сумма денег. Теперь, когда вы вызываете функцию min_change, результат будет таким, как ожидалось:

min_change([1,4,5,10], 8)
> [4, 4]
1 голос
/ 01 апреля 2012

Это вариант задачи о сумме подмножеств.В вашей задаче вы можете выбрать предмет несколько раз.Вы все еще можете использовать аналогичную идею для решения этой проблемы с помощью техники динамического проргамминга.Основная идея состоит в том, чтобы спроектировать функцию F (k, j) так, чтобы F (k, j) = 1 означало, что существует последовательность из arr, сумма которой равна j, а длина равна k.

Формально,базовый случай состоит в том, что F (k, 1) = 1, если существует такое i, что arr [i] = k.Для индуктивного случая F (k, j) = 1, если существует такое i, что arr [i] = m, и F (k-1, jm) = 1.

Наименьшее k сF (k, n) = 1 - длина самой короткой последовательности, которую вы хотите.

Используя метод динамического программирования, вы можете вычислить функцию F без использования рекурсии.Отслеживая дополнительную информацию для каждого F (k, j), вы также можете восстановить самую короткую последовательность.

0 голосов
/ 03 марта 2018

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

Рассмотрим простой случай, когда ваш массив

c = [1, 2, 3]

Вы пишете 5 как комбинацию элементов из C и хотите знать, какая самая короткая такая комбинация. Здесь C - набор значений монет, а 5 - сумма, за которую вы хотите получить сдачу.

Запишем все возможные комбинации:

1 + 1 + 1 + 1 + 1
1 + 1 + 1 + 2
1 + 2 + 2
1 + 1 + 3
2 + 3

Обратите внимание, что до повторного заказа две комбинации одинаковы, например, 2 + 3 = 3 + 2.

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

Например, если c[3] + c[1] + c[2] + c[7] + c[2] + c[3] добавить до S и мы знаем, что 6 - это последовательность элементов минимальной длины от c, которая добавляет до S, тогда, если вы разделите

                              |
S = c[3] + c[1] + c[2] + c[7] | + c[2] + c[3]
                              |

у вас есть то, что 4 является минимальной длиной для последовательностей, которые в сумме составляют c[3] + c[1] + c[2] + c[7] и 2 минимальной длины для последовательностей, которые в сумме составляют c[2] + c[3].

                              |
S = c[3] + c[1] + c[2] + c[7] | + c[2] + c[3] 
                              |
  =        S_left             +     S_right

Как это доказать? В противовес предположим, что длина S_left не является оптимальной, то есть есть более короткая последовательность, которая в сумме составляет S_left. Но тогда мы могли бы написать S как сумму этой более короткой последовательности и S_right, что противоречит тому факту, что длина S минимальна. □

Так как это верно независимо от того, как вы разбили последовательность, вы можете использовать этот результат для построения рекурсивного алгоритма, который следует принципам парадигмы динамического программирования (решение небольших задач, возможно, пропуская вычисления, которые не будут использоваться, памятка или отслеживание вычисленных значений и, наконец, объединение результатов).

ОК, поэтому в приведенном выше небольшом примере это то, как мы должны решить проблему с помощью подхода динамического программирования: предположим, что мы хотим найти кратчайшую последовательность элементов из c = [1, 2, 3] для записи суммы 5. Мы решаем подзадачи, полученные путем вычитания одной монеты: 5 - 1, 5 - 2 и 5 - 3, берем наименьшее решение этих подзадач и добавляем 1 (недостающая монета).

Так что мы можем написать что-то вроде

shortest_seq_length([1, 2, 3], 5) = 
    min( shortest_seq_length([1, 2, 3], 5-1),
         shortest_seq_length([1, 2, 3], 5-2),
         shortest_seq_length([1, 2, 3], 5-3)
        ) + 1

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

Вот код на Python:

def shortest_seq_length(c, S):
    res = {0: 0} # res contains computed results res[i] = shortest_seq_length(c, i)
    for i in range(1, S+1):
        res[i] = min([res[i-x] for x in c if x<=i]) + 1 
    return res[S]

Теперь это работает, за исключением случаев, когда мы не можем заполнить структуру памятки для всех значений i. Это тот случай, когда у нас нет значения 1 в c, поэтому, например, мы не можем сформировать сумму 1, если c = [2, 5], и с помощью вышеуказанной функции мы получим

shortest_seq_length([2, 3], 5)
# ValueError: min() arg is an empty sequence

Таким образом, чтобы решить эту проблему, можно, например, использовать try / catch:

def shortest_seq_length(c, S):
    res = {0: 0} # res is the dictionary containing results for each sum res[i] = shortest_seq_length(c, i)
    for i in range(1, S+1):
        try:
            res[i] = min([res[i-x] for x in c if x<=i and res[i-x] is not None]) +1
        except:
            res[i] = None # takes care of error when [res[i-x] for x in c if x<=i] is empty
    return res[S]

Попробуйте:

print(shortest_seq_length([2, 3], 5))
# 2
print(shortest_seq_length([1, 5, 10, 25], 37))
# 4
print(shortest_seq_length([1, 5, 10], 30))
# 3
print(shortest_seq_length([1, 5, 10], 25))
# 3
print(shortest_seq_length([1, 5, 10], 29))
# 7
print(shortest_seq_length([5, 10], 9))
# None

Одним небольшим улучшением алгоритма является пропуск шага вычисления минимума, когда сумма равна одному из значений / монет, но это можно сделать лучше, если мы напишем цикл для вычисления минимума. Это улучшение, однако, не улучшает общую сложность, которая составляет O(mS), где m = len(c).

...