4

I'm quite new to C# and I have a recursion issue to solve. I want to get the least number of coins possible in this coin change problem. I've adapted an algorithm to it but I need to return an object of type Change which will contain the minimum possible combination.

I've tried to implement an algorithm but I'm having all possible combinations.

using System;
using System.Collections.Generic;
using System.Linq;

// Do not modify change
class Change
{
    public long coin2 = 0;
    public long bill5 = 0;
    public long bill10 = 0;
}

class Solution
{

    // Do not modify method signature
    public static Change OptimalChange(long s)
    {
        Change _change = new Change();
        //To implement here
        return _change;
    }

    public static int GetCombinations(int total, int index, int[] list, List<int> cur)
    {
        if (total == 0)
        {
            foreach (var i in cur)
            {
                Console.Write(i + " ");
            }
            Console.WriteLine();
            return 1;
        }
        if (index == list.Length)
        {
            return 0;
        }
        int ret = 0;
        for (; total >= 0; total -= list[index])
        {
            ret += GetCombinations(total, index + 1, list, cur);
            cur.Add(list[index]);

        }
        for (int i = 0; i < cur.Count(); i++)
        {
            while (cur.Count > i && cur[i] == list[index])
            {
                cur.RemoveAt(i);
            }
        }
        return ret;
    }

}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello Change");
        int s = 41;
        /*Change m = Solution.OptimalChange(s);
        Console.WriteLine("Coins(s) 2euros :" + m.coin2);
        Console.WriteLine("Bill(s) 5euors :" + m.bill5);
        Console.WriteLine("Bill(s) 10euors :" + m.bill10);

        long result = m.coin2*2 + m.bill5*5 + m.bill10*10;

        Console.WriteLine("\nChange given =", + result);*/
        Solution sol = new Solution();
        int[] l = {
            2,
            5,
            10
        };
        Solution.GetCombinations(s, 0, l, new List<int>());
    }
}

Expected Result

Example : The coins available are {2,5,10}

-- I have an input of 12 --

The program should return

Coins(s) 2euros : 1 Bill(s) 5euors : 0 Bill(s) 10euors : 1

-- I have an input of 17 --

The program should return

Coins(s) 2euros : 1 Bill(s) 5euors : 1 Bill(s) 10euors : 1

Alexander Schmidt
  • 5,631
  • 4
  • 39
  • 79
TropicalViking
  • 407
  • 2
  • 9
  • 25
  • Is it required using recursion in the (school) assignment? Because i wouldn't use recursion for this. Use recursion when the same criteria should be used. In this case you just want to try to fit the highest value and use the modulo to check the lower values. – Jeroen van Langen Feb 15 '19 at 11:42
  • Not necessarily – TropicalViking Feb 15 '19 at 11:44
  • 1
    This is a strange version of this problem because you don't say what to do if there is no solution, which is the case for 1 and 3. – Eric Lippert Feb 15 '19 at 15:15
  • Once you've figured it out for coins of value 2, 5 and 10, try coins of value 1, 7 and 15. For an input of 21, it should be three sevens, not one fifteen and six ones. – Eric Lippert Feb 15 '19 at 15:17
  • 1
    The problem is tagged "dynamic programming"; can you explain what dynamic programming technique you are using? – Eric Lippert Feb 15 '19 at 15:19
  • @EricLippert Trying to simplify a complicated problem by breaking it down into simpler sub-problems, in a recursive manner which is being used it. – TropicalViking Feb 15 '19 at 20:08
  • Ok well today is a good day for your education. Start with: that is not dynamic programming. Dynamic programming is a memoization technique often but not necessarily used for improving performance of recursive algorithms. – Eric Lippert Feb 15 '19 at 20:40
  • @EricLippert I've replied to your interrogations on another post. For 1 and 3, there is no solution. So there is a solution for every n > 3. My question is on solving this issue and not on the concept of dynamic programming, else I would have created a post ''What is dynamic programming because Eric Lippert will verify it" – TropicalViking Feb 15 '19 at 21:17
  • I'm asking because *this is a sensible problem to solve with dynamic programming*. I'm wondering if the question is "how do I use dynamic programming to solve this problem?" because you don't appear to be using dynamic programming. – Eric Lippert Feb 15 '19 at 21:56
  • However, you should not start with dynamic programming. Start by building a correct recursive solution. **Do you understand the fundamental structure of a recursive solution?** Start with: what is your base case? Can you show us how you would implement OptimalChange for the base case? – Eric Lippert Feb 15 '19 at 21:59

3 Answers3

8

First off, understand what the basic idea of recursion is:

  • If we can solve the problem without recursion, solve it and return the solution.
  • If we cannot, then reduce the problem to one or more smaller problems, solve each smaller problem recursively, and combine the solutions to solve the larger problem.

Second, understand what the basic idea of dynamic programming is:

  • Recursive solutions often re-compute the same problem many times; it is sometimes more efficient to store the solution rather than re-computing it.

All right, let's attack your problem.

// Do not modify change
class Change
{
    public long coin2 = 0;
    public long bill5 = 0;
    public long bill10 = 0;
}

Pish tosh. This class is terrible. Fix it!

sealed class Change
{
    public long Two { get; }
    public long Five { get; }
    public long Ten { get; }
    public Change(long two, long five, long ten)
    {
      this.Two = two;
      this.Five = five;
      this.Ten = ten;
    }
    public Change AddTwo() => new Change(Two + 1, Five, Ten);
    public Change AddFive() => new Change(Two, Five + 1, Ten);
    public Change AddTen() => new Change(Two, Five, Ten + 1);
    public long Count => Two + Five + Ten;
    public long Total => Two * 2 + Five * 5 + Ten * 10;
}

Start with a data structure that helps you, not one that hurts you.

Now, let's write a recursive function:

public static Change OptimalChange(long s)
{
    // Are we in the base case? Solve the problem.
    // If not, break it down into a smaller problem.
}

What's the base case? There are actually two. If the sum is negative then there is no solution, and if the sum is zero then there is a zero solution.

public static Change OptimalChange(long s)
{
    if (s < 0) 
      return null;
    if (s == 0) 
      return new Change(0, 0, 0);

All right, that's the easy part. What's the hard part? Well, if there is a solution then either it has a two, or it has a five, or it has a ten, right? Or maybe all three. So let's find out.

public static Change OptimalChange(long s)
{
    if (s < 0) 
      return null;
    if (s == 0) 
      return new Change(0, 0, 0);
    Change tryTen = OptimalChange(s - 10)?.AddTen();
    ...

Can you take it from here?

See how much easier the problem gets when you have a data structure that implements the operations you need? Again always create a data structure which helps you.

Next problem: This algorithm is very inefficient. Why? Because we are constantly re-doing problems we have already done. Suppose we are evaluating OptimalChange(22). That calls OptimalChange(12), which calls OptimalChange(7), which calls OptimalChange(5). But OptionalChange(12) also calls OptimalChange(10), which again calls OptimalChange(5). The answer hasn't changed, but we do the computation again.

So, what do we do? We use a dynamic programming technique. There are two ways to do it:

  • Keep on being recursive, and memoize the recursive function.
  • Create an array of change, and fill it in as you go.

But wait, there are more problems than the performance problem. We make the problem smaller by a maximum of 10 and a minimum of 2 every time, but the parameter is a long; it could be billions or trillions. If we have a recursive solution, we'll blow the stack, and if we have an array based solution, it's too large.

We need to be more clever to solve this problem across the given range of possible inputs. Think about it hard; can we solve the problem analytically, without recursion, arrays or long-running loops? Or, equivalently, is there a way to reduce extremely large problems to small problems quickly? The small problem can then be solved with dynamic programming.


As is always the case with homework problems remember that you are bound by the rules of good scholarship. If you use ideas from SO in your homework solutions, you must give credit. Not doing so is plagiarism and will get you expelled from a decent school if you keep it up.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
3

This will print all possible combinations

    static void Main()
    {
        List<int> coins = new List<int>();
        List<int> amounts = new List<int>() { 2, 5, 10 };
        //
        // Compute change for 21 cents.
        //
        Change(coins, amounts, 0, 0, 21);
    }

    static void Change(List<int> coins, List<int> amounts, int highest, int sum, int goal)
    {
        //
        // See if we are done.
        //
        if (sum == goal)
        {
            Display(coins, amounts);
            return;
        }
        //
        // See if we have too much.
        //
        if (sum > goal)
        {
            return;
        }
        //
        // Loop through amounts.
        //
        foreach (int value in amounts)
        {
            //
            // Only add higher or equal amounts.
            //
            if (value >= highest)
            {
                List<int> copy = new List<int>(coins);
                copy.Add(value);
                Change(copy, amounts, value, sum + value, goal);
            }
        }
    }

    static void Display(List<int> coins, List<int> amounts)
    {
        foreach (int amount in amounts)
        {
            int count = coins.Count(value => value == amount);
            Console.WriteLine("{0}: {1}",
                amount,
                count);
        }
        Console.WriteLine();
    }

If you want only the least combination change code to this

static List<int> resultCoins = new List<int>();
    static void Main()
    {
        List<int> amounts = new List<int>() { 2, 5, 10 };
        Change(new List<int>(), amounts, 0, 0, 21);
        Display(resultCoins, amounts);
    }

    static void Change(List<int> coins, List<int> amounts, int highest, int sum, int goal)
    {
        if (sum == goal)
        {
            resultCoins = coins;
            return;
        }
        if (sum > goal)
        {
            return;
        }
        foreach (int value in amounts)
        {
            if (value >= highest)
            {
                List<int> copy = new List<int>(coins);
                copy.Add(value);
                Change(copy, amounts, value, sum + value, goal);
            }
        }
    }

    static void Display(List<int> coins, List<int> amounts)
    {
        foreach (int amount in amounts)
        {
            int count = coins.Count(value => value == amount);
            Console.WriteLine("{0}: {1}", amount, count);
        }
        Console.WriteLine();
    }

Result:

2: 3
5: 1
10: 1
Eugene Chybisov
  • 1,634
  • 2
  • 23
  • 32
  • Your code returning Hello Change 2: 0 5: 0 10: 0 Hence not working. Why use highest,sum etc. The OptimalChange method signature must not be modified. Solution is incorrect, sorry. – TropicalViking Feb 15 '19 at 14:44
  • @TropicalViking check once again, it definitely works. You could take my solution and modify it for your purpose easily – Eugene Chybisov Feb 15 '19 at 14:57
2

What you could do is make a method that returns the denominations that would make up the amount. You could do that by looping around and find the largest denomination that is below or equal to the remainder of the amount. You do that until the remainder is below the lowest denomination available (2 euros). Something like this:

public static IEnumerable<int> MakeChange(int amount)
{
    int[] denominations = {2, 5, 10};
    while (amount >= denominations.Min())
    {
        var denomination = denominations.Where(i => i <= amount).Max();
        amount -= denomination;
        yield return denomination;
    }
}

This would - for 22 - return 10, 10, 2. You could then use the LINQ GroupBy method to group them into denominations and write out the count of each like this:

    foreach (var d in MakeChange(22).GroupBy(i => i))
    {
        Console.WriteLine(d.Key + " " + d.Count());
    }

That would print

10 2
2 1

Meaning you'd need two 10 euro bills and one 2 euro coin to make 22 euro in change.

Hans Kilian
  • 18,948
  • 1
  • 26
  • 35
  • Thanks Hans. If I have 21 for example, the output of the program is 10 2, which means 2 notes of 10. However this is incorrect. What I should get is 1 note of 10, 1 note of 5 and 3 coins of 2 : 10 + 5 + 6. How can this be modified ? thanks again – TropicalViking Feb 15 '19 at 12:25
  • @TropicalViking where are the rules that there is only 1 note of 10? If that is the rule, the `denominations` should be a list, and when one is used, you should remove it from that list. Hans will fix that ;-) – Jeroen van Langen Feb 15 '19 at 12:44
  • @J.vanLangen 10 could be multiple times, but you can't combinate number 21 with two of 10 because we don't have a 1 cent coin – Eugene Chybisov Feb 15 '19 at 12:55
  • @J.vanLangen ,if amount =1 or 3, impossible. if amount = 10, then 1 note of 10 , if amount = 13, then 1 note of 5 + 4 coins of 2. Hope it is clear. – TropicalViking Feb 15 '19 at 13:13