diff --git a/README.md b/README.md index 6f8d36a..7159331 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,14 @@ My LeetCode solutions in Java, focused on clean code and optimal algorithms. ## Solutions -| #* | Problem | Difficulty | Time | Space | [Blind 75][blind-75] | -|-----------------|----------------------------------------------------------------------------------------------------------------------------------|------------|-------------------|--------|----------------------| -| [1][lc-1] | [Two Sum](src/main/java/codes/yam/leetcode/twosum/Solution.java) | Easy | `O(n log n)` | `O(n)` | Yes | -| [9][lc-9] | [Palindrome Number](src/main/java/codes/yam/leetcode/palindromenumber/Solution.java) | Easy | `O(log10(n) / 2)` | `O(1)` | No | -| [70][lc-70] | [Climbing Stairs](src/main/java/codes/yam/leetcode/climbingstairs/Solution.java) | Easy | `O(n)` | `O(1)` | Yes | -| [746][lc-746] | [Min Cost Climbing Stairs](src/main/java/codes/yam/leetcode/mincostclimbingstairs/Solution.java) | Easy | `O(n)` | `O(1)` | No | -| [1653][lc-1653] | [Minimum Deletions to Make String Balanced](src/main/java/codes/yam/leetcode/minimumdeletionstomakestringbalanced/Solution.java) | Medium | `O(n)` | `O(1)` | No | +| #* | Problem | Difficulty | Time | Space | [Blind 75][blind-75] | +|-----------------|----------------------------------------------------------------------------------------------------------------------------------|------------|-------------------|-------------|----------------------| +| [1][lc-1] | [Two Sum](src/main/java/codes/yam/leetcode/twosum/Solution.java) | Easy | `O(n log n)` | `O(n)` | Yes | +| [9][lc-9] | [Palindrome Number](src/main/java/codes/yam/leetcode/palindromenumber/Solution.java) | Easy | `O(log10(n) / 2)` | `O(1)` | No | +| [70][lc-70] | [Climbing Stairs](src/main/java/codes/yam/leetcode/climbingstairs/Solution.java) | Easy | `O(n)` | `O(1)` | Yes | +| [322][lc-322] | [Coin Change](src/main/java/codes/yam/leetcode/coinchange/Solution.java) | Medium | `O(amount × n)` | `O(amount)` | Yes | +| [746][lc-746] | [Min Cost Climbing Stairs](src/main/java/codes/yam/leetcode/mincostclimbingstairs/Solution.java) | Easy | `O(n)` | `O(1)` | No | +| [1653][lc-1653] | [Minimum Deletions to Make String Balanced](src/main/java/codes/yam/leetcode/minimumdeletionstomakestringbalanced/Solution.java) | Medium | `O(n)` | `O(1)` | No | *Problem numbers link to LeetCode; problem names link to solution source. @@ -22,6 +23,8 @@ My LeetCode solutions in Java, focused on clean code and optimal algorithms. [lc-70]: https://leetcode.com/problems/climbing-stairs/ +[lc-322]: https://leetcode.com/problems/coin-change/ + [lc-746]: https://leetcode.com/problems/min-cost-climbing-stairs/ [lc-1653]: https://leetcode.com/problems/minimum-deletions-to-make-string-balanced/ diff --git a/src/main/java/codes/yam/leetcode/coinchange/Solution.java b/src/main/java/codes/yam/leetcode/coinchange/Solution.java new file mode 100644 index 0000000..fe6c431 --- /dev/null +++ b/src/main/java/codes/yam/leetcode/coinchange/Solution.java @@ -0,0 +1,29 @@ +package codes.yam.leetcode.coinchange; + +import java.util.Arrays; + +/** + * Solution for the Coin Change problem. + * + * + */ +class Solution { + int coinChange(int[] coins, int amount) { + int[] dp = new int[amount + 1]; + Arrays.fill(dp, 1, amount + 1, amount + 1); + for (int a = 1; a <= amount; a++) { + for (int c : coins) { + if (c <= a) { + dp[a] = Math.min(dp[a], 1 + dp[a - c]); + } + } + } + if (dp[amount] > amount) { + return -1; + } + return dp[amount]; + } +} diff --git a/src/main/java/codes/yam/leetcode/coinchange/SolutionNaive.java b/src/main/java/codes/yam/leetcode/coinchange/SolutionNaive.java new file mode 100644 index 0000000..e4aeb59 --- /dev/null +++ b/src/main/java/codes/yam/leetcode/coinchange/SolutionNaive.java @@ -0,0 +1,37 @@ +package codes.yam.leetcode.coinchange; + +/** + * Solution for the Coin Change problem. + * + *

Naive bottom-up DP using {@code -1} as the sentinel for unreachable states. See {@link + * Solution} for the simplified version using {@code amount + 1} as infinity. + * + *

+ */ +class SolutionNaive { + int coinChange(int[] coins, int amount) { + if (amount == 0) return 0; + int[] dp = new int[amount + 1]; + for (int a = 1; a <= amount; a++) { + boolean useMin = true; + int min = Integer.MAX_VALUE; + for (int c : coins) { + if (c > a) continue; + if (c == a) { + dp[a] = 1; + useMin = false; + continue; + } + if (dp[a - c] == -1) continue; + min = Math.min(min, 1 + dp[a - c]); + } + if (useMin) { + dp[a] = min == Integer.MAX_VALUE ? -1 : min; + } + } + return dp[amount]; + } +} diff --git a/src/main/java/codes/yam/leetcode/coinchange/package-info.java b/src/main/java/codes/yam/leetcode/coinchange/package-info.java new file mode 100644 index 0000000..586ff94 --- /dev/null +++ b/src/main/java/codes/yam/leetcode/coinchange/package-info.java @@ -0,0 +1,22 @@ +/** + * Solutions for the "Coin Change" problem on LeetCode. + * + * + * + *

Solution progression: + * + *

    + *
  1. {@link codes.yam.leetcode.coinchange.SolutionNaive} — bottom-up DP using {@code -1} as the + * sentinel for unreachable states. Requires special-casing {@code c == amount} and a {@code + * useMin} flag to avoid treating unset cells as valid. + *
  2. {@link codes.yam.leetcode.coinchange.Solution} — same bottom-up DP, but uses {@code amount + * + 1} as the infinity sentinel. Since {@code ∞ + 1 = ∞} and {@code min(∞, x) = x}, the + * sentinel is algebraically compatible with the recurrence and all special cases collapse. + *
+ * + * @see Problem Link + */ +package codes.yam.leetcode.coinchange; diff --git a/src/test/java/codes/yam/leetcode/coinchange/SolutionNaiveTest.java b/src/test/java/codes/yam/leetcode/coinchange/SolutionNaiveTest.java new file mode 100644 index 0000000..03b0909 --- /dev/null +++ b/src/test/java/codes/yam/leetcode/coinchange/SolutionNaiveTest.java @@ -0,0 +1,14 @@ +package codes.yam.leetcode.coinchange; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class SolutionNaiveTest { + @ParameterizedTest + @MethodSource("codes.yam.leetcode.coinchange.TestCases#cases") + void coinChange(int[] coins, int amount, int expected) { + assertEquals(expected, new SolutionNaive().coinChange(coins, amount)); + } +} diff --git a/src/test/java/codes/yam/leetcode/coinchange/SolutionTest.java b/src/test/java/codes/yam/leetcode/coinchange/SolutionTest.java new file mode 100644 index 0000000..bf4f191 --- /dev/null +++ b/src/test/java/codes/yam/leetcode/coinchange/SolutionTest.java @@ -0,0 +1,14 @@ +package codes.yam.leetcode.coinchange; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class SolutionTest { + @ParameterizedTest + @MethodSource("codes.yam.leetcode.coinchange.TestCases#cases") + void coinChange(int[] coins, int amount, int expected) { + assertEquals(expected, new Solution().coinChange(coins, amount)); + } +} diff --git a/src/test/java/codes/yam/leetcode/coinchange/TestCases.java b/src/test/java/codes/yam/leetcode/coinchange/TestCases.java new file mode 100644 index 0000000..b3b307c --- /dev/null +++ b/src/test/java/codes/yam/leetcode/coinchange/TestCases.java @@ -0,0 +1,14 @@ +package codes.yam.leetcode.coinchange; + +import java.util.stream.Stream; +import org.junit.jupiter.params.provider.Arguments; + +@SuppressWarnings("unused") +class TestCases { + static Stream cases() { + return Stream.of( + Arguments.of(new int[] {1, 2, 5}, 11, 3), + Arguments.of(new int[] {2}, 3, -1), + Arguments.of(new int[] {1}, 0, 0)); + } +}