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.
+ *
+ *
+ * - Time Complexity:
O(amount * n) where {@code n} is {@code coins.length}
+ * - Space Complexity:
O(amount)
+ *
+ */
+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.
+ *
+ *
+ * - Time Complexity:
O(amount * n) where {@code n} is {@code coins.length}
+ * - Space Complexity:
O(amount)
+ *
+ */
+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.
+ *
+ *
+ * - Slug:
coin-change
+ * - Difficulty: Medium
+ *
+ *
+ * Solution progression:
+ *
+ *
+ * - {@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.
+ *
- {@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));
+ }
+}