Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions 322/memo.md
Original file line number Diff line number Diff line change
@@ -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があることも知らなかった

15 changes: 15 additions & 0 deletions 322/sol1.py
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

math.isinf() も選択肢としてありそうですね。infinityは普通の値と扱いが異なるので、個人的には値をそのまま比べるよりも math.isinf() を使いたい気持ちがあります、が好みの問題だと思います。

Copy link
Copy Markdown
Owner Author

@tom4649 tom4649 Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

専用の関数がある場合には使うべきですね。math.isinf()を以降覚えておこうと思います。

13 changes: 13 additions & 0 deletions 322/sol2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.python.org/3/library/typing.html#typing.List

Deprecated alias to list.

とあるように、古いバージョンを扱う場合以外は list をそのまま型ヒントとして使用したい気持ちがあります。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

調べてみるとtypes.GenericAliasというものみたいですね。listに引数を与えてlist[int]とするとtypes.GenericAliasになるようです。
https://docs.python.org/3/library/stdtypes.html#types-genericalias

num_changes = [float("inf")] * (amount + 1)
num_changes[0] = 0
Comment on lines +3 to +4
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2行目で何となく想像は付くのですが、添字の説明がコメントであると親切に思いました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the minimum number of coins required to make up amount i

としました。確かに初見ではわかりにくいかもしれないです。

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]
18 changes: 18 additions & 0 deletions 322/sol2_revised.py
Original file line number Diff line number Diff line change
@@ -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]
25 changes: 25 additions & 0 deletions 322/sol3.py
Original file line number Diff line number Diff line change
@@ -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}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visit済みの何なのか分かりにくいので、命名で補足すると良さそうです。visited_sum ですかね。


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]
26 changes: 26 additions & 0 deletions 322/sol3_revised.py
Original file line number Diff line number Diff line change
@@ -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]
32 changes: 32 additions & 0 deletions 322/sol4.py
Original file line number Diff line number Diff line change
@@ -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