如何使用递归为C#中的硬币更改问题获取最少的组合

时间:2019-02-15 11:34:33

标签: c# dynamic-programming coin-change

我是C#的新手,我有一个递归问题要解决。我希望此硬币找零问题中的硬币数量最少。我已经为其调整了一种算法,但是我需要返回一个类型为Change的对象,该对象将包含最小可能的组合。

我尝试实现一种算法,但是我拥有所有可能的组合。

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>());
    }
}

预期结果

示例:可用硬币为{2,5,10}

-我的输入是12-

程序应返回

硬币2euros:1 法案5欧元:0 比尔10euors:1

-我的输入为17-

程序应返回

硬币2euros:1 法案5欧元:1 比尔10euors:1

3 个答案:

答案 0 :(得分:8)

首先,了解递归的基本概念是:

  • 如果我们可以不递归地解决问题,请解决并返回解决方案。
  • 如果不能,则将问题简化为一个或多个较小的问题,递归解决每个较小的问题,然后组合解决方案以解决较大的问题。

第二,了解动态编程的基本思想是什么

    递归解决方案通常会多次重新计算同一问题;有时存储解决方案比重新计算解决方案更为有效。

好的,让我们解决您的问题。

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

Pish tosh。此类很糟糕。修复它!

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;
}

从一种可以帮助您的数据结构开始,而不是一种会伤害您的数据结构

现在,让我们编写一个递归函数:

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

基本情况是什么?实际上有两个。 如果总和为负,则没有解决方案,并且如果总和为零,则存在零解决方案

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

好吧,这很容易。难点是什么?好吧,如果有解决方案,那么要么有两个解决方案,要么有五个解决方案,或者有十个解决方案,对吗?或全部三个。因此,让我们找出答案。

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();
    ...

你能从这里拿走吗?

看看当您拥有实现所需操作的数据结构时,问题变得容易多了吗?再次总是创建一个对您有帮助的数据结构

下一个问题:该算法效率很低。为什么?因为我们一直在重复做问题,所以我们已经做了。假设我们正在评估OptimalChange(22)。调用OptimalChange(12),调用OptimalChange(7),调用OptimalChange(5)。但是OptionalChange(12)也调用OptimalChange(10),再次调用OptimalChange(5)。答案没有改变,但是我们再次进行计算。

那么,我们该怎么办? 我们使用动态编程技术。有两种方法可以做到:

  • 继续递归,并记住递归函数。
  • 创建一个更改数组,并随时进行填写。

但是,等等,问题多于性能问题。我们使问题每次减小最多10个,最小减小2个,但是该参数很长。可能是数十亿或数万亿。如果我们有一个递归的解决方案,那么我们就要花大价钱了;如果我们有一个基于数组的解决方案,那就太大了。

我们需要更加聪明,以在给定的可能输入范围内解决此问题。认真考虑一下; 我们可以解析地解决问题,而无需递归,数组或长时间运行的循环吗?或者,等效地,有没有一种方法可以将巨大的问题迅速减少为小问题?小问题可以通过动态编程解决。


与家庭作业问题一样,请记住,您必须遵守良好的学业规则。如果您在作业解决方案中使用SO的想法,则您必须给予好评。不这样做是窃,如果继续坚持下去,就会把你从一所体面的学校开除。

答案 1 :(得分:2)

您可以做的是创建一个方法,该方法返回将构成金额的面额。您可以通过循环查找并找到小于或等于剩余金额的最大面额来做到这一点。您要这样做,直到剩余金额低于可用的最低面额(2欧元)。像这样:

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;
    }
}

这将-对于22-返回10、10、2。然后,您可以使用LINQ GroupBy方法将它们分组为面额,并像这样写出每个的计数:

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

可以打印

10 2
2 1

这意味着您需要两张10欧元的钞票和一枚2欧元的硬币才能产生22欧元的零钱。

答案 2 :(得分:1)

这将打印所有可能的组合

    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();
    }

如果您只想要最小的组合更改代码

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();
    }

结果:

2: 3
5: 1
10: 1