-
Notifications
You must be signed in to change notification settings - Fork 0
Solved Arai60/322. Coin Change #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| ## Step 1. Initial Solution | ||
|
|
||
| - 順番に大きい方から割れるだけ割って、残りを引き継いでいく方法 | ||
| - 辞書型に残りの金額と使った枚数を保持 | ||
| - やっぱりリストで十分なので修正 | ||
| - 明らかに計算コストは高いが他に思いつかなかったのでこれで実装 | ||
| - コインの枚数は12なのでsortやcoinのループ数自体は大きな問題にはならないが辞書のコピーとその各要素に対する実行で最大O(amount ^ 2) | ||
| - 更新のタイミングが難しく、サンプルケースを通す実装に1時間かけてしまった | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def coinChange(self, coins: List[int], amount: int) -> int: | ||
| remaining_to_min_coin = [None] * amount + [0] | ||
| for remaining in range(amount, 0, -1): | ||
| if remaining_to_min_coin[remaining] is None: continue | ||
| for coin in coins: | ||
| if coin > remaining: continue | ||
| for i in range(remaining // coin, 0, -1): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indexでないものにiを使うのはやや違和感があるかもしれません |
||
| if remaining_to_min_coin[remaining - coin * i] is None: | ||
| remaining_to_min_coin[remaining - coin * i] = remaining_to_min_coin[remaining] + i | ||
| continue | ||
| remaining_to_min_coin[remaining - coin * i] = \ | ||
| min(remaining_to_min_coin[remaining - coin * i], remaining_to_min_coin[remaining] + i) | ||
| if remaining_to_min_coin[0] is None: | ||
| return -1 | ||
| return remaining_to_min_coin[0] | ||
| ``` | ||
|
|
||
| - ここからさらにしばらく考えてみたが、よく考えたら各ステップでそのコインがたどり着ける場所を全部考慮する必要はないことに気が付いた | ||
| - シンプルに各ステップでは1枚コインを追加して行ける場所だけ記録すればよい | ||
| - これで無事に通った | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def coinChange(self, coins: List[int], amount: int) -> int: | ||
| remaining_to_min_coin = [None] * amount + [0] | ||
| for remaining in range(amount, 0, -1): | ||
| if remaining_to_min_coin[remaining] is None: continue | ||
| for coin in coins: | ||
| if coin > remaining: continue | ||
| if remaining_to_min_coin[remaining - coin] is None: | ||
| remaining_to_min_coin[remaining - coin] = remaining_to_min_coin[remaining] + 1 | ||
| continue | ||
| remaining_to_min_coin[remaining - coin] = \ | ||
| min(remaining_to_min_coin[remaining - coin], remaining_to_min_coin[remaining] + 1) | ||
| if remaining_to_min_coin[0] is None: | ||
| return -1 | ||
| return remaining_to_min_coin[0] | ||
| ``` | ||
|
|
||
|
|
||
| ### Complexity Analysis | ||
|
|
||
| - 時間計算量:O(n * k) | ||
| - コインの枚数nと総額k | ||
| - 空間計算量:O(k) | ||
| - 総額分の長さのリストを利用 | ||
|
|
||
| ## Step 2. Alternatives | ||
|
|
||
| - math.infを初期値に使うこともできる | ||
| - 最後の判定にmath.isinfを用いると綺麗に書ける | ||
| - https://github.com/tokuhirat/LeetCode/pull/40/files#diff-8f6b81efa221a5236aad48fc3551b5d0288080123ad0271a4536d46d0c207302R62 | ||
| - 再帰で解く方法 | ||
| - cacheが自然に出てきて欲しい | ||
| - lru_cacheを使うならサイズを気にしたい | ||
| - https://github.com/Fuminiton/LeetCode/pull/40/files#r2073709258 | ||
| - 始めは以下の実装でamount==coinの判定をしていたが==0にすれば例外処理が不要になることに気づいた | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def coinChange(self, coins: List[int], amount: int) -> int: | ||
| @cache | ||
| def numCoinsToAmount(amount: int) -> int: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://google.github.io/styleguide/pyguide.html#316-naming |
||
| min_coins = math.inf | ||
| for coin in coins: | ||
| if amount < 0: | ||
| continue | ||
| if amount == 0: | ||
| return 0 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. amountのチェックはfor分岐の前に持ってきたほうが自然でしょうか。 |
||
| min_coins = \ | ||
| min(min_coins, numCoinsToAmount(amount - coin) + 1) | ||
| return min_coins | ||
| num_coins = numCoinsToAmount(amount) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 関数の切れ目が認識しづらいため、個人的には inner function のあとに空行を入れたいです。 |
||
| if isinf(num_coins): | ||
| return -1 | ||
| return num_coins | ||
| ``` | ||
|
|
||
| - 最小値を求めるときにGeneratorを使うパターンもあるらしい | ||
| - https://github.com/TORUS0818/leetcode/pull/42/files#r1904039471 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def coinChange(self, coins: List[int], amount: int) -> int: | ||
| @cache | ||
| def numCoinsToAmount(amount: int) -> int: | ||
| if amount == 0: | ||
| return 0 | ||
| if amount < 0: | ||
| return -1 | ||
| def listNumCoinsPossible(): | ||
| for coin in coins: | ||
| num_coins = numCoinsToAmount(amount - coin) | ||
| if num_coins == -1: | ||
| continue | ||
| yield num_coins + 1 | ||
| return min(listNumCoinsPossible(), default=-1) | ||
| return numCoinsToAmount(amount) | ||
| ``` | ||
|
|
||
|
|
||
| ## Step 3. Final Solution | ||
|
|
||
| - 再帰も悪くない気がしたが、今回はDP最後の問題ということで練習しておいた | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def coinChange(self, coins: List[int], amount: int) -> int: | ||
| min_coins_to_amount = [0] + [-1] * amount | ||
| for i, num_coins in enumerate(min_coins_to_amount): | ||
| if num_coins == -1: | ||
| continue | ||
| for coin in coins: | ||
| if i + coin > amount: | ||
| continue | ||
| if min_coins_to_amount[i + coin] == -1 or \ | ||
| min_coins_to_amount[i + coin] > num_coins + 1: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この条件分岐の方法は思いつきませんでした。minなどの比較をしなくてよくなるのでシンプルになっていいですね! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. min_coins_to_amount の値を大きな数字で初期化すれば、 if 文が不要になると思います。 INFINITY = 100000
min_coins_to_amount = [0] + [INFINITY] * amount
for i, num_coins in enumerate(min_coins_to_amount):
if num_coins == -1:
continue
for coin in coins:
if i + coin > amount:
continue
min_coins_to_amount[i + coin] = min(min_coins_to_amount[i + coin], num_coins + 1)
if min_coins_to_amount[-1] == INFINITY:
return -1
return min_coins_to_amount[-1]
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 確かに最後に一回分岐作るだけの方が良さそうですね |
||
| min_coins_to_amount[i + coin] = num_coins + 1 | ||
| return min_coins_to_amount[-1] | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
同一の list の中に、 None と 0 という異なる型の値が含まれている点に違和感を感じました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
そうなんですね、同じ型で比較できる形の方が自然なんですね
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
の方が
一気に違う型を入れて初期化するよりは違和感は少ないかもしれません