Здесь мы рассмотрим вопросы динамического программирования (DP), где нам нужно найти оптимальное решение для всех значений в цели.
Вам дан целочисленный массив coins
, представляющий монеты разного номинала, и целочисленный массив amount
, представляющий общую сумму денег.
Возвратите наименьшее наименьшее количество монет, необходимое для получения этой суммы. Если эту сумму денег нельзя компенсировать ни одной комбинацией монет, верните -1
.
Вы можете предположить, что у вас есть бесконечное количество монет каждого вида.
Для DP нужно помнить, что нужно определить подзадачу и использовать решение подзадачи для получения ответа. Давайте составим таблицу для вычисления всех возможных комбинаций монет [1, 2, 5]
и суммы до 11
.
Мы видим, что для создания 0
$ нам нужно 0
монет. Чтобы сделать 1
$, нам понадобится 1 счет номиналом 1
$. Чтобы получить сумму 2
$, мы можем использовать 2 счета 1
$ или 1 счет 2
$ и так далее. Для определенной суммы мы берем min
этой суммы и другие комбинации amount-coin
+1.
Ярко-желтая строка является оптимальным количеством для каждой суммы.
Код для вычисления этой таблицы:
import math from typing import List, Dict from collections import defaultdict def coin_change_helper(coins: List[int], cache: Dict[int, int], amount: int) -> int: if amount in cache: return cache[amount] if amount == 0: return 0 cache[amount] = math.inf for coin in coins: if amount - coin >= 0: cache[amount] = min(cache[amount], \ coin_change_helper(coins, cache, amount-coin)+1) return cache[amount] def coin_change(coins: List[int], amount: int) -> int: cache = defaultdict(int) res = coin_change_helper(coins, cache, amount) return res if res != math.inf else -1 coins = [1, 2, 5] amount = 11 print(coin_change(coins, amount)) # prints 3 coins1 = [2] amount1 = 3 print(coin_change(coins1, amount1)) # prints -1
Для рекурсивного шага уже вычисленной суммы мы будем получать значение из cache
, поэтому нам не нужно вводить цикл for
; то, что до сих пор ломало мне голову, когда я работал над проблемой без дедлайна.
Теперь давайте реализуем восходящий подход:
import math from typing import List def coin_change(coins: List[int], amount: int) -> int: dp = [math.inf] * (amount+1) dp[0] = 0 for coin in coins: for amt in range(1, amount+1): if amt - coin >= 0: dp[amt] = min(dp[amt], dp[amt-coin]+1) return dp[amount] if dp[amount] != math.inf else -1 coins = [1, 2, 5] amount = 11 print(coin_change(coins, amount)) # prints 3 coins1 = [2] amount1 = 3 print(coin_change(coins1, amount1)) # prints -1
Мы строим решение подзадачи. Ниже приведена таблица, в которой показано количество монет, необходимое для каждого номинала в [1, 2, 5]
на сумму 11
.
Мы вернем dp[amount]
, что равно 3
в последней строке и последнем элементе.
Бонус: если вы этого не заметили, выделенная желтым цветом строка в запомненном табличном значении совпадает с последней строкой в решении dp
.
Решим еще одну аналогичную задачу:
Идеальные квадраты
Получив целое число n
, верните наименьшее число совершенных квадратных чисел, сумма которых равна n
.
Идеальный квадрат – это целое число, являющееся квадратом целого числа; другими словами, это произведение некоторого целого числа на самого себя. Например, 1
, 4
, 9
и 16
являются правильными квадратами, а 3
и 11
— нет.
Вот запомненная версия:
import math import collections from typing import List def perfect_square_helper(squares: List[int], cache: List[int], n: int) -> int: if n in cache: return cache[n] if n == 0: return 0 cache[n] = math.inf for square in squares: if n - square >= 0: cache[n] = min(cache[n], \ perfect_square_helper(squares, cache, n-square)+1) print(cache) return cache[n] def perfect_square(n: int) -> int: squares = [i**i for i in range(1, int(n**1/2)+1)] cache = collections.defaultdict(int) res = perfect_square_helper(squares, cache, n) return res n = 12 print(perfect_square(n)) # prints 3
Вот версия снизу вверх:
import math def perfect_square(n: int) -> int: squares = [i**i for i in range(int(n**1/2)+1)] dp = [math.inf] * (n+1) dp[0] = 0 for square in squares: for i in range(1, n+1): if i-square >= 0: dp[i] = min(dp[n], dp[i-square]+1) # print(dp) return dp[-1] n = 12 print(perfect_square(n)) # prints 3
Реализация такая же! 😵
Это все для этого поста. Надеюсь, вы получаете более мягкое и приятное знакомство с DP.
До следующего раза ✨.
Вдохновение:
- Литкод
Вы можете поддержать меня в Патреоне!