找到最佳账单组合来支付特定价值

时间:2016-05-19 14:21:35

标签: c# algorithm

我正在开发具有以下要求的支付系统游戏:

1-假设游戏中的账单是:10,5,4,3,2,1

2- AI需要选择所需的最少数量的账单来支付确切的金额,即如果需要支付8并且AI有(4,4,3,3,2)......他可以选择( 4,4)但不是(3,3,2)

3-如果AI无法使用他所拥有的账单赚取确切的金额,他应该选择这样的组合,使得它给出具有最小差值的金额,即,如果所需的支付金额是7和AI有下面的账单(10,5,4,4),他选择了(4,4)给玩家1超过所需金额。

以下是我的代码

//sortedValues is a list containing my bills in descending order
//ChosenCardsToPay is a list for the bills I choose to pay with

public void PreparePayment(int neededAmount)
{

    int remainingAmount = neededAmount;

    int chosenAmount;

    while (remainingAmount > 0) 
    {
        chosenAmount = 0;

        foreach (int moneyValue in sortedValues )           
        {
            if (moneyValue <= remainingAmount) 
            {   chosenCardsToPay.Add (moneyValue); //Add Bill Value to my candidate list
                remainingAmount = remainingAmount - moneyValue;
                chosenAmount = moneyValue;
                break;
            }   
        }
        if (chosenAmount != 0)
            sortedValues.Remove (moneyValue);//Remove Chosen Bill from Initial List
        else //If all bill values are greater than remaining amount, i choose the bill with smallest value and add to the candidate list
        {
            chosenAmount = sortedValues.Last();
            sortedValues.Remove(chosenAmount);
            chosenCardsToPay.Add (chosenAmount);
            remainingAmount = remainingAmount - moneyValue;
        }
    }
}

大部分时间都可以正常工作,但请注意这种情况:所需金额为4,AI有(3,2,2)作为账单。使用上述算法,AI选择(3,2)最佳答案为(2,2)。

有人可以指导我正确思考这个问题吗?谢谢!

1 个答案:

答案 0 :(得分:1)

这是我提出的递归解决方案。我们的想法是跟踪“过剩”并在您找到完全匹配时立即返回。如果没有找到完全匹配,你只需根据它们的数量,然后根据需要多少账单来计算超额数量,然后选择第一个账单。为了在完全匹配上获得最少的账单,请确保bills按降序排序。如果没有办法用给定的一组账单来支付金额,这也将返回一个空序列。

public static IEnumerable<int> CoverAmount(
    int amount, List<int> bills, HashSet<int> used = null)
{
    if (used == null)
        used = new HashSet<int>();
    if (amount <= 0)
        return Enumerable.Empty<int>();

    var overages = new List<Tuple<List<int>, int>>();
    for(int index = 0; index < bills.Count; index++)
    {
        var bill = bills[index];
        if (used.Contains(index))
            continue;
        if (bill > amount)
        {
            overages.Add(Tuple.Create(new List<int> { bill }, bill - amount));
        }
        else if (bill == amount)
        {
            return Enumerable.Repeat(bill, 1);
        }
        else
        {
            used.Add(index);
            var bestSub = CoverAmount(amount - bill, bills, used).ToList();
            used.Remove(index);
            bestSub.Add(bill);
            var sum = bestSub.Sum();
            if (sum == amount)
            {
                return bestSub;
            }

            if (sum > amount)
            {
                overages.Add(Tuple.Create(bestSub, sum - amount));
            }
        }
    }

    return overages
        .OrderBy(t => t.Item2)
        .ThenBy(t => t.Item1.Count)
        .FirstOrDefault()?.Item1 ?? Enumerable.Empty<int>();

    // OR this if you are not using C# 6
    // var bestOverage = overages
    //    .OrderBy(t => t.Item2)
    //    .ThenBy(t => t.Item1.Count)
    //    .FirstOrDefault();
    // return bestOverage == null ? Enumerable.Empty<int>() : bestOverage.Item1;
}

以下代码

Console.WriteLine(string.Join(", ", CoverAmount(8, new List<int> { 4, 4, 3, 3, 2 })));
Console.WriteLine(string.Join(", ", CoverAmount(7, new List<int> { 10, 5, 4, 4 })));
Console.WriteLine(string.Join(", ", CoverAmount(4, new List<int> { 3, 2, 2 })));
Console.WriteLine(string.Join(", ", CoverAmount(10, new List<int> { 11, 6, 5 })));

将产生此输出

  

4,4

     

4,4

     

2,2

     

11