合并数字列表以获得给定限制内的最大金额

时间:2014-01-07 12:32:20

标签: c#

假设您有一个低于10的数字列表 - 例如{7,6,5,4} 目标是将它们组合成总和> = 10并且我想要实现最大数量的组合而不重用任何数字。此外,如果可能,解决方案应尽可能接近10。

E.g。
7 + 4& 6 + 5 = 2个解(11& 11-到10的最大距离是1)
7 + 5& 6 + 4 = 2个解(12& 10-到10的最大距离是2)
7 + 6 = 1溶液(因为剩余的5 + 4 <10)

我的第一种方法是取最高数字,然后加上最小数字。如果这不等于或超过限制,那么我会添加下一个最低数字 它导致了这段代码:

        IList<int> numbers = new List<int>{6, 4, 2, 2, 5, 2};
        var numbersOrdered = numbers.OrderByDescending(x => x).ToList();
        var resultList= new List<List<int>>();

        int highIndex = 0;
        int lowIndex = numbersOrdered.Count() - 1;

        List<int> result = new List<int>();
        result.Add(numbersOrdered[highIndex]);
        int sum = numbersOrdered[highIndex];

        while (highIndex < lowIndex)
        {
            sum += numbersOrdered[lowIndex]; 
            result.Add(numbersOrdered[lowIndex]);
            if (sum >= 10)
            {
                //Found valid combination - add to result list
                resultList.Add(result);

                //Move index pointers
                highIndex++;
                lowIndex--;

                //Reset temporary result list
                result = new List<int>();
                result.Add(numbersOrdered[highIndex]);
                sum = numbersOrdered[highIndex];
            }
            else
            {
                //Didn't find valid combination. Try to add the next lowest number
                lowIndex--;
            }
        }
然而,它确实很快就变成了错误的做法。因此,我目前正在尝试这种看似更稳固的方法,但仍然存在一个小问题。

private void FindBestMatches(List<int> numbersOrdered, List<List<int>> resultList)
    {
        while (true)
        {
            var bestMatch = new List<int>();
            var combinations = GetPowerSet(numbersOrdered);
            foreach (var combination in combinations)
            {
                int sum = combination.Sum();
                if (sum == 10) //Found perfect match - use this combination
                {
                    bestMatch = combination.ToList();
                    break;
                }
                else if (sum > 10 && (!bestMatch.Any() || sum < bestMatch.Sum()))
                {
                    bestMatch = combination.ToList();
                }
            }
            //If we found a valid solution, remove these values from the ordered list
            if (bestMatch.Any())
            {
                AddCombinationToResults(bestMatch, numbersOrdered, resultList);
                continue;
            }
            break;
        }
    }

    private static void AddCombinationToResults(IEnumerable<int> combination, List<int> numbersOrdered, List<List<int>> resultList)
    {
        //Remove the numbers in the result from the ordered numbers list
        combination.ForEach(item => numbersOrdered.Remove(item));
        //Add the result to the list of pools
        resultList.Add(combination.ToList());
    }

    public IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list)
    {
        return from m in Enumerable.Range(0, 1 << list.Count)
               select
                   from i in Enumerable.Range(0, list.Count)
                   where (m & (1 << i)) != 0
                   select list[i];
    }

使用这种方法时,似乎我得到了正确数量的解决方案,但它们没有得到优化 - 例如与{7,6,5,4}我得到{6,4}和{7,5}而不是{7,4}和{6,5},这很明显,因为我寻找最小的总和。但我无法弄清楚找到11和11而不是10和12的方法?

1 个答案:

答案 0 :(得分:0)

经过多次试验和错误后,我想出了这个解决方案。它并不完美或快速,但它会在我的情况下完成。

    private List<List<decimal>> FindBestMatches(List<decimal> numbersOrdered)
    {
        var resultList = new List<List<decimal>>();

        if (numbersOrdered.Sum() < 10)
        {
            return resultList;
        }

        var possibleSumsWithinRange = GetPowerSet(numbersOrdered).Select(combination => combination.Sum()).Distinct().Where(sum => sum>= 10 && sum < 20).OrderBy(sum => sum);
        //Remove highest sum if there are more than one possible sum
        if (possibleSumsWithinRange.Count() > 1)
        {
            possibleSumsWithinRange.ToList().RemoveAt(possibleSumsWithinRange.Count() - 1);   
        }

        //Go through all possible sums in order to find best result (lowest highest sum)
        foreach (var possibleSum in possibleSumsWithinRange)
        {
            //Console.WriteLine("Sum:" + possibleSum);
            var subResultList = new List<List<decimal>>();
            var numbers = numbersOrdered.ToList();
            while (true)
            {
                var combinations = GetPowerSet(numbers);
                var bestMatch = new List<decimal>();
                foreach (var combination in combinations)
                {
                    decimal sum = combination.Sum();
                    if (sum < possibleSum)
                    {
                        continue;
                    }
                    if (sum == possibleSum) //Found perfect match - use this combination
                    {
                        bestMatch = combination.ToList();
                        break;
                    }
                    else if (sum > possibleSum && (!bestMatch.Any() || sum < bestMatch.Sum()))
                    {
                        bestMatch = combination.ToList();
                    }
                }
                //If we found a valid solution, remove these values from the ordered list and use same approach on the remaining values
                if (bestMatch.Any())
                {
                    bestMatch.ForEach(item => numbers.Remove(item));
                    subResultList.Add(bestMatch.ToList());
                    continue;
                }
                //If the new solution is the first or better than the previous, then promote it to result
                if (!resultList.Any() || IsBetterSolution(resultList, subResultList))
                {
                    resultList = subResultList.ToList();
                }
                break;
            }
        }
        return resultList;
    }

    public bool IsBetterSolution(List<List<decimal>> previousSolution, List<List<decimal>> newSolution)
    {
        //If the new solution has more pools than the previous, then it is considered better
        if (newSolution.Count > previousSolution.Count)
        {
            return true;
        }
        //If they have the same amount of pools, then choose the solution with the lowest highest sum
        return (newSolution.Count == previousSolution.Count) && (highestSum(newSolution) < highestSum(previousSolution));
    }

    public decimal highestSum(List<List<decimal>> solution)
    {
        return solution.Max(x => x.Sum());
    }

    public IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list)
    {
        return from m in Enumerable.Range(0, 1 << list.Count)
               select
                   from i in Enumerable.Range(0, list.Count)
                   where (m & (1 << i)) != 0
                   select list[i];
    }