Skip to content

Commit

Permalink
Solve LeetCode #518 'Coin Change II' [Medium] using recursion with me…
Browse files Browse the repository at this point in the history
…moization.
  • Loading branch information
eminencegrs committed Dec 13, 2024
1 parent 3b42ede commit 92d5d38
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
46 changes: 46 additions & 0 deletions LeetCode/src/LeetCode.Challenges/CoinChangeII/RecursionWithMemo.cs
Original file line number Diff line number Diff line change
@@ -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)];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<object[]> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Collections;

namespace LeetCode.Challenges.UnitTests.CoinChangeII;

public class TestData : IEnumerable<object[]>
{
public IEnumerator<object[]> 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();
}

0 comments on commit 92d5d38

Please sign in to comment.