Skip to content

Commit f32cb1b

Browse files
update post
1 parent 605857f commit f32cb1b

File tree

4 files changed

+125
-5
lines changed

4 files changed

+125
-5
lines changed

_posts/2024-02-26-leetcode-322.md

+125-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
layout: post
3-
title: (Leetcode) 322 - Coin Change
3+
title: (Leetcode) 322 - Coin Change 풀이
44
categories: [스터디-알고리즘]
5-
tags: [파이썬, 알고리즘, python, algorithm, Leetcode, DP, coin]
5+
tags: [파이썬, 알고리즘, python, algorithm, Leetcode, DP, coin, java, stream]
66
date: 2024-02-26 22:00:00 +0900
77
image:
88
path: /assets/images/2024-02-26-leetcode-322/page1.png
@@ -22,7 +22,7 @@ DP 문제이다.
2222

2323
### 처음 생각
2424

25-
처음에는 큰 동전부터 넣어서 될 때까지 하면 베스트 일 것이라고 생각했다. 하지만 그렇게 간단하지는 않았다.
25+
처음에는 큰 동전부터 넣어서 될 때까지 하면 베스트 일 것이라고 생각했다. 하지만 그렇게 간단하지는 않았다.
2626

2727
예를들어 `[100, 30, 1]` 이라는 동전이 있다고 해보자.
2828
이때 주어지는 amount는 120 이라고 하면
@@ -31,7 +31,7 @@ DP 문제이다.
3131

3232
### 중간 생각
3333

34-
그래서 모든 경우의 수를 다 찾아봐야 겠다고 생각했다.
34+
그래서 모든 경우의 수를 다 찾아봐야 겠다고 생각했다.
3535
처음에는 잘 먹히는 듯 보였으나, 역시나 복잡한 경우로 인해 타임아웃이 발생되었다.
3636

3737
```python
@@ -129,17 +129,137 @@ print(solution.coinChange([1, 2, 5], 10))
129129
![example page5](/assets/images/2024-02-26-leetcode-322/page5.png)
130130

131131
### 시간복잡도
132+
132133
- 이중 반복문 사용
133134
- 바깥쪽 반복문 : amount 만큼 반복 (O(n))
134135
- 안쪽 반복문 : coins 리스트의 length 만큼 반복 (O(m))
135136

136137
`O(n * m)` 의 시간 복잡도를 가짐
137138

138139
### 공간복잡도
140+
139141
amount 만큼 dp array를 생성함
140142

141143
`O(n)` 의 공간 복잡도를 가짐
142144

143145
## 결론
144146

145-
DP 문제를 더 많이 접해봐야겠다는 생각이 들었다.
147+
DP 문제를 더 많이 접해봐야겠다는 생각이 들었다.
148+
149+
## java 로 다시 풀기 (24.07.09)
150+
151+
먼저는 stream으로 풀었다. 이미 들린 dp pointer 일 때, 기존에 등록된 값보다 클 경우 (`dp[currentPointer] > dp[amount] + 1`) 이후 dfs 단계를 생략하도록 한 것이 포인트이다.
152+
153+
처음으로 `dp[0]`에 도달했다고 해서 최소값이 아니기 때문에 전체 케이스를 고려해야 하는데 그렇다고 진짜로 전체 케이스를 확인해본다면 timeout이 발생된다. 따라서 이 조건문을 찾아내지 못하면 timeout이 발생된다.
154+
155+
```java
156+
class Solution {
157+
public int coinChange(int[] coins, int amount) {
158+
if(amount == 0) {
159+
return 0;
160+
}
161+
162+
int[] dp = new int[amount + 1];
163+
164+
List<Integer> sortedCoins = Arrays.stream(coins).boxed()
165+
.sorted(Collections.reverseOrder())
166+
.toList();
167+
168+
sortedCoins.forEach(coin -> dfs(dp, sortedCoins, amount, coin));
169+
170+
return dp[0] == 0 ? -1 : dp[0];
171+
}
172+
173+
void dfs(int[] dp, List<Integer> coins, int amount, int selectedCoin) {
174+
int currentPointer = amount - selectedCoin;
175+
if (currentPointer < 0) {
176+
return;
177+
}
178+
179+
if (dp[currentPointer] == 0 || dp[currentPointer] > dp[amount] + 1) {
180+
dp[currentPointer] = dp[amount] + 1;
181+
coins.forEach(coin -> dfs(dp, coins, currentPointer, coin));
182+
}
183+
}
184+
}
185+
```
186+
187+
stream 의 경우 for loop 보다는 느리지만 이해하기에는 훨씬 좋다. 더 직관적이다.
188+
189+
![java-using-for-loop](/assets/images/2024-02-26-leetcode-322/java-using-for-loop.png)
190+
191+
다만 성능을 더 최적화 하기 위해서 for loop를 이용해서 풀도록 바꾸면 다음과 같다.
192+
193+
```java
194+
class Solution {
195+
public int coinChange(int[] coins, int amount) {
196+
if(amount == 0) {
197+
return 0;
198+
}
199+
200+
int[] dp = new int[amount + 1];
201+
202+
Arrays.sort(coins);
203+
for (int i = coins.length - 1; i > -1; i--) {
204+
dfs(dp, coins, amount, coins[i]);
205+
}
206+
207+
return dp[0] == 0 ? -1 : dp[0];
208+
}
209+
210+
void dfs(int[] dp, int[] coins, int amount, int selectedCoin) {
211+
int currentPointer = amount - selectedCoin;
212+
if (currentPointer < 0) {
213+
return;
214+
}
215+
216+
if (dp[currentPointer] == 0 || dp[currentPointer] > dp[amount] + 1) {
217+
dp[currentPointer] = dp[amount] + 1;
218+
for (int i = coins.length - 1; i > -1; i--) {
219+
dfs(dp, coins, currentPointer, coins[i]);
220+
}
221+
}
222+
}
223+
}
224+
```
225+
226+
![java-using-for-loop](/assets/images/2024-02-26-leetcode-322/java-using-for-loop.png)
227+
228+
stream을 사용했을 때보다 시간이 단축된 것을 볼 수 있다.
229+
230+
### TS, SC
231+
232+
코인의 수를 n 이라고 했을 때, `O(n * amount ^ 2)` 의 시간복잡도와 `O(amount)` 의 공간복잡도를 가진다.
233+
234+
## java 모범 답안 (더 효율적인 방법)
235+
236+
위 코드는 dp 라는 array를 사용하긴 했지만, 사실 brute force 에 가까운 방법이다.
237+
238+
아래와 같이 작성하면 더 효율적으로 동작한다.
239+
240+
```java
241+
public class Solution {
242+
public int coinChange(int[] coins, int amount) {
243+
int max = amount + 1;
244+
int[] dp = new int[amount + 1];
245+
Arrays.fill(dp, max);
246+
dp[0] = 0;
247+
248+
for (int i = 1; i <= amount; i++) {
249+
for (int j = 0; j < coins.length; j++) {
250+
if (coins[j] <= i) {
251+
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
252+
}
253+
}
254+
}
255+
256+
return dp[amount] > amount ? -1 : dp[amount];
257+
}
258+
}
259+
```
260+
261+
### TS, SC
262+
263+
코인의 수를 n 이라고 했을 때, `O(n * amount)` 의 시간복잡도와 `O(amount)` 의 공간복잡도를 가진다.
264+
265+
![java-using-dp](/assets/images/2024-02-26-leetcode-322/java-using-dp.png)
Loading
Loading
Loading

0 commit comments

Comments
 (0)