From f58ff3c66c088d36eb039e12ce708aabc4ef3780 Mon Sep 17 00:00:00 2001 From: eminencegrs Date: Sat, 14 Dec 2024 00:07:59 +0100 Subject: [PATCH] Solve LeetCode #518 'Coin Change II' [Medium] using recursion with memoization. --- .../CoinChangeII/BacktrackingSolution.cs | 2 +- .../CoinChangeII/RecursionWithMemo.cs | 46 ++++++++++++++ .../CoinChangeII/BacktrackingSolutionTests.cs | 60 +----------------- .../CoinChangeII/RecursionWithMemoTests.cs | 15 +++++ .../CoinChangeII/TestData.cs | 62 +++++++++++++++++++ 5 files changed, 127 insertions(+), 58 deletions(-) create mode 100644 LeetCode/src/LeetCode.Challenges/CoinChangeII/RecursionWithMemo.cs create mode 100644 LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/RecursionWithMemoTests.cs create mode 100644 LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/TestData.cs diff --git a/LeetCode/src/LeetCode.Challenges/CoinChangeII/BacktrackingSolution.cs b/LeetCode/src/LeetCode.Challenges/CoinChangeII/BacktrackingSolution.cs index 06ee5eb..6fa80ec 100644 --- a/LeetCode/src/LeetCode.Challenges/CoinChangeII/BacktrackingSolution.cs +++ b/LeetCode/src/LeetCode.Challenges/CoinChangeII/BacktrackingSolution.cs @@ -2,7 +2,7 @@ public static class BacktrackingSolution { - public static int CoinChange(int[] coins, int amount) + public static int CoinChange(int amount, int[] coins) { var count = 0; Backtrack(0, amount); diff --git a/LeetCode/src/LeetCode.Challenges/CoinChangeII/RecursionWithMemo.cs b/LeetCode/src/LeetCode.Challenges/CoinChangeII/RecursionWithMemo.cs new file mode 100644 index 0000000..7f57838 --- /dev/null +++ b/LeetCode/src/LeetCode.Challenges/CoinChangeII/RecursionWithMemo.cs @@ -0,0 +1,46 @@ +namespace LeetCode.Challenges.CoinChangeII; + +public static class RecursionWithMemo +{ + public static int Change(int amount, int[] coins) + { + var cache = new Dictionary<(int, int), int>(); + + // Start recursion with the full amount and the first coin. + return CountCombinations(0, amount); + + // A function for recursion with memoization. + int CountCombinations(int index, int remaining) + { + // Base case: if remaining amount is 0, a valid combination is found. + if (remaining == 0) + { + return 1; + } + + // Base case: if index is out of bounds or remaining is negative, no combinations are possible. + if (index >= coins.Length || remaining < 0) + { + return 0; + } + + // Check if result is already cached. + if (cache.TryGetValue((index, remaining), out int cachedResult)) + { + return cachedResult; + } + + // Recursive exploration: + // 1. Include the current coin: + // Stay at the same index, subtract its value from the remaining. + int include = CountCombinations(index, remaining - coins[index]); + + // 2. Exclude the current coin: move to the next index. + int exclude = CountCombinations(index + 1, remaining); + + // Store the result in the dictionary and return it + cache[(index, remaining)] = include + exclude; + return cache[(index, remaining)]; + } + } +} diff --git a/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/BacktrackingSolutionTests.cs b/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/BacktrackingSolutionTests.cs index 4c01bac..7176d66 100644 --- a/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/BacktrackingSolutionTests.cs +++ b/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/BacktrackingSolutionTests.cs @@ -7,63 +7,9 @@ namespace LeetCode.Challenges.UnitTests.CoinChangeII; public class BacktrackingSolutionTests { [Theory] - [MemberData(nameof(CoinChangeTestCases))] - public void GivenCoins_WhenCoinChange_ThenResultAsExpected(int[] coins, int amount, int expected) + [ClassData(typeof(TestData))] + public void GivenCoins_WhenCoinChange_ThenResultAsExpected(int amount, int[] coins, int expected) { - BacktrackingSolution.CoinChange(coins, amount).ShouldBe(expected); - } - - public static IEnumerable CoinChangeTestCases() - { - // (5) - // / \ - // -1 (4) -2 (3) - // / \ \ - // -1 (3) -2 (2) -2 (1) - // / \ | | - // -1 (2) -2 (0) (0) (X) - // / \ - // -1 (1) -2 (X) - // / - // (0) - // - // Valid Paths: - // [1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 2, 2] - yield return [new[] { 1, 2 }, 5, 3]; - - // (5) - // / | \ - // -1 (4) -2 (3) -5 (0) - // / | | - // -1 (3) -2 (2) -2 (1) - // / | | | - // -1 (2)-2 (0) (0) (X) - // / | - // -1 (1)-2 (X) - // / - // (0) - // - // Valid Paths: - // [1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 2, 2], [5] - yield return [new[] { 1, 2, 5 }, 5, 4]; - - yield return [new[] { 2 }, 3, 0]; - yield return [new[] { 10 }, 10, 1]; - yield return [new[] { 1, 3, 4 }, 6, 4]; - yield return [new[] { 5 }, 5, 1]; - yield return [new[] { 5 }, 2, 0]; - yield return [new[] { 2, 5, 10 }, 1, 0]; - yield return [new[] { 10, 5 }, 20, 3]; - yield return [new[] { 1, 5, 10, 25 }, 30, 18]; - yield return [new[] { 2, 3 }, 7, 1]; - yield return [new[] { 1, 2, 5 }, 100, 541]; - yield return [new[] { 7, 3, 2 }, 8, 2]; - yield return [new[] { 1, 7, 10 }, 14, 4]; - yield return [new[] { 1, 3, 4, 7 }, 15, 22]; - yield return [new[] { 25, 50, 100 }, 30, 0]; - yield return [new[] { 9, 6, 5, 1 }, 11, 6]; - - // LeetCode: Time Limit Exceeded - yield return [new[] { 3,5,7,8,9,10,11 }, 500, 35502874]; + BacktrackingSolution.CoinChange(amount, coins).ShouldBe(expected); } } diff --git a/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/RecursionWithMemoTests.cs b/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/RecursionWithMemoTests.cs new file mode 100644 index 0000000..e835ba4 --- /dev/null +++ b/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/RecursionWithMemoTests.cs @@ -0,0 +1,15 @@ +using LeetCode.Challenges.CoinChangeII; +using Shouldly; +using Xunit; + +namespace LeetCode.Challenges.UnitTests.CoinChangeII; + +public class RecursionWithMemoTests +{ + [Theory] + [ClassData(typeof(TestData))] + public void GivenCoins_WhenCoinChange_ThenResultAsExpected(int amount, int[] coins, int expected) + { + RecursionWithMemo.Change(amount, coins).ShouldBe(expected); + } +} diff --git a/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/TestData.cs b/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/TestData.cs new file mode 100644 index 0000000..201d5d2 --- /dev/null +++ b/LeetCode/tests/LeetCode.Challenges.UnitTests/CoinChangeII/TestData.cs @@ -0,0 +1,62 @@ +using System.Collections; + +namespace LeetCode.Challenges.UnitTests.CoinChangeII; + +public class TestData : IEnumerable +{ + public IEnumerator GetEnumerator() + { + // (5) + // / \ + // -1 (4) -2 (3) + // / \ \ + // -1 (3) -2 (2) -2 (1) + // / \ | | + // -1 (2) -2 (0) (0) (X) + // / \ + // -1 (1) -2 (X) + // / + // (0) + // + // Valid Paths: + // [1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 2, 2] + yield return [5, new[] { 1, 2 }, 3]; + + // (5) + // / | \ + // -1 (4) -2 (3) -5 (0) + // / | | + // -1 (3) -2 (2) -2 (1) + // / | | | + // -1 (2)-2 (0) (0) (X) + // / | + // -1 (1)-2 (X) + // / + // (0) + // + // Valid Paths: + // [1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 2, 2], [5] + yield return [5, new[] { 1, 2, 5 }, 4]; + + yield return [3, new[] { 2 }, 0]; + yield return [10, new[] { 10 }, 1]; + yield return [6, new[] { 1, 3, 4 }, 4]; + yield return [5, new[] { 5 }, 1]; + yield return [2, new[] { 5 }, 0]; + yield return [1, new[] { 2, 5, 10 }, 0]; + yield return [20, new[] { 10, 5 }, 3]; + yield return [30, new[] { 1, 5, 10, 25 }, 18]; + yield return [7, new[] { 2, 3 }, 1]; + yield return [100, new[] { 1, 2, 5 }, 541]; + yield return [8, new[] { 7, 3, 2 }, 2]; + yield return [14, new[] { 1, 7, 10 }, 4]; + yield return [15, new[] { 1, 3, 4, 7 }, 22]; + yield return [30, new[] { 25, 50, 100 }, 0]; + yield return [11, new[] { 9, 6, 5, 1 }, 6]; + + // LeetCode: Time Limit Exceeded + yield return [500, new[] { 3, 5, 7, 8, 9, 10, 11 }, 35502874]; + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); +}