From 13b216c251ee00c4be82fe03391f3a6cac32edad Mon Sep 17 00:00:00 2001 From: tom4649 Date: Mon, 30 Mar 2026 08:57:53 +0900 Subject: [PATCH 1/2] 322. Coin Change --- 322/memo.md | 36 ++++++++++++++++++++++++++++++++++++ 322/sol1.py | 15 +++++++++++++++ 322/sol2.py | 13 +++++++++++++ 322/sol3.py | 25 +++++++++++++++++++++++++ 322/sol4.py | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 322/memo.md create mode 100644 322/sol1.py create mode 100644 322/sol2.py create mode 100644 322/sol3.py create mode 100644 322/sol4.py diff --git a/322/memo.md b/322/memo.md new file mode 100644 index 0000000..c75c9fa --- /dev/null +++ b/322/memo.md @@ -0,0 +1,36 @@ +# 322. Coin Change + +そのまま思いついた方法として、メモ化再帰を実装: sol1.py +- 一発で通ったので気持ち良い + +再帰関数を使わずに書く + +- 動的計画法: + +- 最短経路と考えればBFSで書ける +- tableを保持したsol3.pyを書いたが、coinをソートすれば早期breakができること、levelがそのまま答えになることに気づき、改良: sol4.py + +## 計算量 +amount = O(n), len(coins) = O(m) +### sol1.py +- 時間計算量 O(nm), 空間計算量 O(n) (=スタックフレーム、キャッシュ) +### sol2.py +- 時間計算量 O(nm), 空間計算量 O(n) +### sol4.py +- 時間計算量 O(nm), 空間計算量 O(n) + + +動的計画法 +https://www.slideshare.net/slideshow/ss-3578511/3578511#1 + +自分のsol2.pyはもらうDP + +https://github.com/TORUS0818/leetcode/pull/42#discussion_r1904039471 +> -1 の扱いが気に入らなかったので Generator 使うのは考えてみました。 + +https://github.com/mamo3gr/arai60/blob/322_coin-change/322_coin-change/memo.md +> 再帰+メモ化で、amount - coin でできる最小枚数をGeneratorで返させて、min(gen, default=-1) で最小値を取る。 自分もそれぞれ配列に入れてから min は思いついたが、最後に配列を舐めるのもなあ…と思って止めた。Generatorなら消費しながらminを取ってくれそう。 + +generatorなら作れない場合の無効値を流さず書くことができる(minを取るときにリストを作る必要もない) +minにdefaultがあることも知らなかった + diff --git a/322/sol1.py b/322/sol1.py new file mode 100644 index 0000000..0e1a1ee --- /dev/null +++ b/322/sol1.py @@ -0,0 +1,15 @@ +import functools + + +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + @functools.cache + def minimum_coins(target_amount): + if target_amount == 0: + return 0 + if target_amount < 0: + return float("inf") + return min([minimum_coins(target_amount - coin) + 1 for coin in coins]) + + num_changes = minimum_coins(amount) + return -1 if num_changes == float("inf") else num_changes diff --git a/322/sol2.py b/322/sol2.py new file mode 100644 index 0000000..a323943 --- /dev/null +++ b/322/sol2.py @@ -0,0 +1,13 @@ +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + num_changes = [float("inf")] * (amount + 1) + num_changes[0] = 0 + for target_amount in range(1, amount + 1): + for coin in coins: + if target_amount - coin >= 0: + num_changes[target_amount] = min( + num_changes[target_amount], + num_changes[target_amount - coin] + 1, + ) + + return -1 if num_changes[amount] == float("inf") else num_changes[amount] diff --git a/322/sol3.py b/322/sol3.py new file mode 100644 index 0000000..a3cd4ce --- /dev/null +++ b/322/sol3.py @@ -0,0 +1,25 @@ +from collections import deque + + +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + minimum_coins = [float("inf")] * (amount + 1) + minimum_coins[0] = 0 + coins_sorted = sorted(coins) + + frontier = deque([0]) + visited = {0} + + while frontier: + current_sum = frontier.popleft() + for coin in coins_sorted: + next_sum = current_sum + coin + if next_sum in visited or next_sum > amount: + continue + minimum_coins[next_sum] = min( + minimum_coins[next_sum], minimum_coins[current_sum] + 1 + ) + frontier.append(next_sum) + visited.add(current_sum) + + return -1 if minimum_coins[amount] == float("inf") else minimum_coins[amount] diff --git a/322/sol4.py b/322/sol4.py new file mode 100644 index 0000000..fc69abc --- /dev/null +++ b/322/sol4.py @@ -0,0 +1,32 @@ +from collections import deque + + +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + if amount == 0: + return 0 + + coins_sorted = sorted(coins) + visited = [False] * (amount + 1) + visited[0] = True + + frontier = deque([0]) + steps = 0 + + while frontier: + steps += 1 + len_current_frontier = len(frontier) + for _ in range(len_current_frontier): + current_sum = frontier.popleft() + for coin in coins_sorted: + next_sum = current_sum + coin + if next_sum > amount: + break + if visited[next_sum]: + continue + if next_sum == amount: + return steps + visited[next_sum] = True + frontier.append(next_sum) + + return -1 From c245f87fd0a9d3be584136d9eb91382e4f7aeb09 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Tue, 31 Mar 2026 19:17:03 +0900 Subject: [PATCH 2/2] Add suggested changes --- 322/sol2_revised.py | 18 ++++++++++++++++++ 322/sol3_revised.py | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 322/sol2_revised.py create mode 100644 322/sol3_revised.py diff --git a/322/sol2_revised.py b/322/sol2_revised.py new file mode 100644 index 0000000..36b5e9c --- /dev/null +++ b/322/sol2_revised.py @@ -0,0 +1,18 @@ +import math + + +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + num_changes = [float("inf")] * ( + amount + 1 + ) # the minimum number of coins required to make up amount i + num_changes[0] = 0 + for target_amount in range(1, amount + 1): + for coin in coins: + if target_amount - coin >= 0: + num_changes[target_amount] = min( + num_changes[target_amount], + num_changes[target_amount - coin] + 1, + ) + + return -1 if math.inf(num_changes[amount]) else num_changes[amount] diff --git a/322/sol3_revised.py b/322/sol3_revised.py new file mode 100644 index 0000000..af3ea69 --- /dev/null +++ b/322/sol3_revised.py @@ -0,0 +1,26 @@ +from collections import deque +import math + + +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + minimum_coins = [float("inf")] * (amount + 1) + minimum_coins[0] = 0 + coins_sorted = sorted(coins) + + frontier = deque([0]) + visited_sum = {0} + + while frontier: + current_sum = frontier.popleft() + for coin in coins_sorted: + next_sum = current_sum + coin + if next_sum in visited_sum or next_sum > amount: + continue + minimum_coins[next_sum] = min( + minimum_coins[next_sum], minimum_coins[current_sum] + 1 + ) + frontier.append(next_sum) + visited_sum.add(current_sum) + + return -1 if math.isinf(minimum_coins[amount]) else minimum_coins[amount]