diff --git a/_posts/2024-02-26-leetcode-322.md b/_posts/2024-02-26-leetcode-322.md index a936bda..ae282c2 100644 --- a/_posts/2024-02-26-leetcode-322.md +++ b/_posts/2024-02-26-leetcode-322.md @@ -1,8 +1,8 @@ --- layout: post -title: (Leetcode) 322 - Coin Change +title: (Leetcode) 322 - Coin Change 풀이 categories: [스터디-알고리즘] -tags: [파이썬, 알고리즘, python, algorithm, Leetcode, DP, coin] +tags: [파이썬, 알고리즘, python, algorithm, Leetcode, DP, coin, java, stream] date: 2024-02-26 22:00:00 +0900 image: path: /assets/images/2024-02-26-leetcode-322/page1.png @@ -22,7 +22,7 @@ DP 문제이다. ### 처음 생각 -처음에는 큰 동전부터 넣어서 될 때까지 하면 베스트 일 것이라고 생각했다. 하지만 그렇게 간단하지는 않았다. +처음에는 큰 동전부터 넣어서 될 때까지 하면 베스트 일 것이라고 생각했다. 하지만 그렇게 간단하지는 않았다. 예를들어 `[100, 30, 1]` 이라는 동전이 있다고 해보자. 이때 주어지는 amount는 120 이라고 하면 @@ -31,7 +31,7 @@ DP 문제이다. ### 중간 생각 -그래서 모든 경우의 수를 다 찾아봐야 겠다고 생각했다. +그래서 모든 경우의 수를 다 찾아봐야 겠다고 생각했다. 처음에는 잘 먹히는 듯 보였으나, 역시나 복잡한 경우로 인해 타임아웃이 발생되었다. ```python @@ -129,6 +129,7 @@ print(solution.coinChange([1, 2, 5], 10)) ![example page5](/assets/images/2024-02-26-leetcode-322/page5.png) ### 시간복잡도 + - 이중 반복문 사용 - 바깥쪽 반복문 : amount 만큼 반복 (O(n)) - 안쪽 반복문 : coins 리스트의 length 만큼 반복 (O(m)) @@ -136,10 +137,129 @@ print(solution.coinChange([1, 2, 5], 10)) `O(n * m)` 의 시간 복잡도를 가짐 ### 공간복잡도 + amount 만큼 dp array를 생성함 `O(n)` 의 공간 복잡도를 가짐 ## 결론 -DP 문제를 더 많이 접해봐야겠다는 생각이 들었다. \ No newline at end of file +DP 문제를 더 많이 접해봐야겠다는 생각이 들었다. + +## java 로 다시 풀기 (24.07.09) + +먼저는 stream으로 풀었다. 이미 들린 dp pointer 일 때, 기존에 등록된 값보다 클 경우 (`dp[currentPointer] > dp[amount] + 1`) 이후 dfs 단계를 생략하도록 한 것이 포인트이다. + +처음으로 `dp[0]`에 도달했다고 해서 최소값이 아니기 때문에 전체 케이스를 고려해야 하는데 그렇다고 진짜로 전체 케이스를 확인해본다면 timeout이 발생된다. 따라서 이 조건문을 찾아내지 못하면 timeout이 발생된다. + +```java +class Solution { + public int coinChange(int[] coins, int amount) { + if(amount == 0) { + return 0; + } + + int[] dp = new int[amount + 1]; + + List sortedCoins = Arrays.stream(coins).boxed() + .sorted(Collections.reverseOrder()) + .toList(); + + sortedCoins.forEach(coin -> dfs(dp, sortedCoins, amount, coin)); + + return dp[0] == 0 ? -1 : dp[0]; + } + + void dfs(int[] dp, List coins, int amount, int selectedCoin) { + int currentPointer = amount - selectedCoin; + if (currentPointer < 0) { + return; + } + + if (dp[currentPointer] == 0 || dp[currentPointer] > dp[amount] + 1) { + dp[currentPointer] = dp[amount] + 1; + coins.forEach(coin -> dfs(dp, coins, currentPointer, coin)); + } + } +} +``` + +stream 의 경우 for loop 보다는 느리지만 이해하기에는 훨씬 좋다. 더 직관적이다. + +![java-using-for-loop](/assets/images/2024-02-26-leetcode-322/java-using-for-loop.png) + +다만 성능을 더 최적화 하기 위해서 for loop를 이용해서 풀도록 바꾸면 다음과 같다. + +```java +class Solution { + public int coinChange(int[] coins, int amount) { + if(amount == 0) { + return 0; + } + + int[] dp = new int[amount + 1]; + + Arrays.sort(coins); + for (int i = coins.length - 1; i > -1; i--) { + dfs(dp, coins, amount, coins[i]); + } + + return dp[0] == 0 ? -1 : dp[0]; + } + + void dfs(int[] dp, int[] coins, int amount, int selectedCoin) { + int currentPointer = amount - selectedCoin; + if (currentPointer < 0) { + return; + } + + if (dp[currentPointer] == 0 || dp[currentPointer] > dp[amount] + 1) { + dp[currentPointer] = dp[amount] + 1; + for (int i = coins.length - 1; i > -1; i--) { + dfs(dp, coins, currentPointer, coins[i]); + } + } + } +} +``` + +![java-using-for-loop](/assets/images/2024-02-26-leetcode-322/java-using-for-loop.png) + +stream을 사용했을 때보다 시간이 단축된 것을 볼 수 있다. + +### TS, SC + +코인의 수를 n 이라고 했을 때, `O(n * amount ^ 2)` 의 시간복잡도와 `O(amount)` 의 공간복잡도를 가진다. + +## java 모범 답안 (더 효율적인 방법) + +위 코드는 dp 라는 array를 사용하긴 했지만, 사실 brute force 에 가까운 방법이다. + +아래와 같이 작성하면 더 효율적으로 동작한다. + +```java +public class Solution { + public int coinChange(int[] coins, int amount) { + int max = amount + 1; + int[] dp = new int[amount + 1]; + Arrays.fill(dp, max); + dp[0] = 0; + + for (int i = 1; i <= amount; i++) { + for (int j = 0; j < coins.length; j++) { + if (coins[j] <= i) { + dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); + } + } + } + + return dp[amount] > amount ? -1 : dp[amount]; + } +} +``` + +### TS, SC + +코인의 수를 n 이라고 했을 때, `O(n * amount)` 의 시간복잡도와 `O(amount)` 의 공간복잡도를 가진다. + +![java-using-dp](/assets/images/2024-02-26-leetcode-322/java-using-dp.png) diff --git a/assets/images/2024-02-26-leetcode-322/java-using-dp.png b/assets/images/2024-02-26-leetcode-322/java-using-dp.png new file mode 100644 index 0000000..a9e45cb Binary files /dev/null and b/assets/images/2024-02-26-leetcode-322/java-using-dp.png differ diff --git a/assets/images/2024-02-26-leetcode-322/java-using-for-loop.png b/assets/images/2024-02-26-leetcode-322/java-using-for-loop.png new file mode 100644 index 0000000..446a5c9 Binary files /dev/null and b/assets/images/2024-02-26-leetcode-322/java-using-for-loop.png differ diff --git a/assets/images/2024-02-26-leetcode-322/java-using-stream.png b/assets/images/2024-02-26-leetcode-322/java-using-stream.png new file mode 100644 index 0000000..7661e8c Binary files /dev/null and b/assets/images/2024-02-26-leetcode-322/java-using-stream.png differ