Skip to content

Commit 00739f9

Browse files
committed
solve(w04): 322. Coin Change
1 parent b5ab62d commit 00739f9

File tree

1 file changed

+113
-0
lines changed

1 file changed

+113
-0
lines changed

coin-change/seungriyou.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# https://leetcode.com/problems/coin-change/
2+
3+
from functools import cache
4+
from typing import List
5+
import math
6+
7+
class Solution:
8+
def coinChange_n(self, coins: List[int], amount: int) -> int:
9+
"""
10+
[Complexity]
11+
- TC: O(n * amount)
12+
- SC: O(n * amount)
13+
14+
[Approach]
15+
각 coin을 무한히 많이 사용할 수 있으므로 unbounded knapsack problem 이다.
16+
이때, 가치를 최대화하는 것 == 동전의 개수를 최소화 하는 것이다.
17+
따라서 2D DP로 풀 수 있다.
18+
"""
19+
20+
INF = amount + 1
21+
n = len(coins)
22+
23+
# dp[i][j] = i번째 coin까지 사용했을 때, j 만큼의 amount를 만들 수 있는 coin의 최소 개수
24+
dp = [[INF] * (amount + 1) for _ in range(n + 1)]
25+
dp[0][0] = 0
26+
27+
for i in range(1, n + 1): # -- coin
28+
dp[i][0] = 0
29+
for j in range(1, amount + 1): # -- amount
30+
if j < coins[i - 1]:
31+
dp[i][j] = dp[i - 1][j] # 현재 coin을 넣을 수 없음
32+
else:
33+
dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1) # min(현재 coin을 넣지 X 경우, 현재 coin을 넣는 경우)
34+
35+
return dp[n][amount] if dp[n][amount] != INF else -1
36+
37+
def coinChange_1(self, coins: List[int], amount: int) -> int:
38+
"""
39+
[Complexity]
40+
- TC: O(n * amount)
41+
- SC: O(amount)
42+
43+
[Approach]
44+
매 단계에서 다음의 두 값만 확인하므로, 2D DP를 rolling array 방식으로 1D DP로 space optimize 할 수 있다.
45+
- dp[i - 1][j]
46+
- dp[i][j - coins[i - 1]]
47+
"""
48+
49+
INF = amount + 1
50+
51+
dp = [INF] * (amount + 1)
52+
dp[0] = 0
53+
54+
for coin in coins:
55+
for amnt in range(coin, amount + 1):
56+
dp[amnt] = min(dp[amnt], dp[amnt - coin] + 1) # min(현재 coin을 넣지 X 경우, 현재 coin을 넣는 경우)
57+
58+
return dp[amount] if dp[amount] != INF else -1
59+
60+
def coinChange_b(self, coins: List[int], amount: int) -> int:
61+
"""
62+
[Complexity]
63+
- TC: O(n * amount) (금액 1 ~ amount 각각에 대해 len(coins) 만큼 확인)
64+
- SC: O(amount) (seen & q)
65+
66+
[Approach]
67+
BFS로 최단거리를 찾듯이 접근해도 된다. 이때의 최단거리란 최소 개수를 의미한다.
68+
"""
69+
from collections import deque
70+
71+
q = deque([(0, 0)]) # (총 금액, coin 개수)
72+
seen = {0} # 이미 확인한 총 금액
73+
74+
while q:
75+
amnt, n = q.popleft()
76+
77+
# base condition
78+
if amnt == amount:
79+
return n
80+
81+
# iter
82+
for coin in coins:
83+
if (new_amnt := amnt + coin) <= amount and new_amnt not in seen:
84+
q.append((new_amnt, n + 1))
85+
seen.add(new_amnt)
86+
87+
return -1
88+
89+
def coinChange(self, coins: List[int], amount: int) -> int:
90+
"""
91+
[Complexity]
92+
- TC: O(n * amount) (금액 0 ~ amount, 각각 len(coins) 만큼 확인)
93+
- SC: O(amount) (@cache 저장 공간, call stack)
94+
95+
[Approach]
96+
bottom-up이었던 DP 뿐만 아니라, 더 직관적인 top-down 접근도 가능하다.
97+
이때 @cache를 사용하면 memoization을 통해 더 최적화할 수 있다.
98+
"""
99+
100+
@cache
101+
def dp(amnt):
102+
# base condition
103+
if amnt == 0:
104+
return 0
105+
if amnt < 0:
106+
return math.inf
107+
108+
# recur
109+
return min(dp(amnt - coin) + 1 for coin in coins)
110+
111+
res = dp(amount)
112+
113+
return res if res != math.inf else -1

0 commit comments

Comments
 (0)