【问题标题】:Find elements which sum is closest to a given value [duplicate]查找总和最接近给定值的元素[重复]
【发布时间】:2015-05-15 22:38:32
【问题描述】:

我已经搜索并阅读了有关此问题的算法,但它们似乎不适用于这种情况,或者不够清楚。

我有一个 unsigned 值的List<decimal>,我试图在其中找到 sum 最接近指定值 N的元素p>

列表大小可变,平均有 500 个元素。 性能不是这个解决方案的优先考虑

如果没有找到解决方案,实现的方法应该返回一个单一解决方案或一个空列表。

现有的不止一个,选一个元素少的。

Example:

N = 15.00

Elements = {
0.10, //EDIT: This shouldn't be here
7.00,
7.00,
14.10,
15.90,
}

Solutions = {
0.10 + 7.00 + 7.00, //EDIT: This shouldn't be here
14.10,
15.90,
} 

Final Solution = {14.10} // or 15.90

【问题讨论】:

  • 这需要进一步澄清。答案的可能解决方案是什么? Element 中任何数字的任意组合?如果是这样,您的最终解决方案没有意义,因为 14.10 + 0.10 将是 14.20,它比 14.10 更接近 15。
  • 我很确定{14.10 + 0.10} 比您提出的“正确”答案更好。无论如何,动态编程可以非常有效地完成这项工作。修剪后的动态规划,更是如此。
  • 这与Knapsack Problem密切相关,同样的方法也可以(稍作修改将硬限制改为软限制)
  • 分支和绑定解决方案可能是有效的,但任何精确的解决方案都将是 O(2^n),因为这实际上是子集和。
  • 我希望它保持开放,我希望看到 C# 的实现。

标签: c# algorithm


【解决方案1】:

首先,添加这个方便的组合扩展:(credits to this answer)

public static class EnumerableExtensions
{
    public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
    {
        return k == 0 ? new[] { new T[0] } :
          elements.SelectMany((e, i) =>
            elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] { e }).Concat(c)));
    }
}

然后:

private IEnumerable<decimal> ClosestSum(decimal[] elements, decimal n)
{
    var target = Enumerable.Range(1, elements.Length)
        .SelectMany(p => elements.Combinations(p))
        .OrderBy(p => Math.Abs((decimal)p.Sum() - n))
        .ThenBy(p => p.Count())
        .FirstOrDefault();
    return target ?? new decimal[] { };
}

【讨论】:

    【解决方案2】:

    这可以通过dynamic programming 方法在时间上与输入呈线性关系。

        private class Solution
        {
            public int StartIndex;
            public int EndIndex;
            public decimal Sum;
            public int Length
            {
                get { return EndIndex - StartIndex + 1; }
            }
    
        }
    
        static List<decimal> Solve(List<decimal> elements, decimal target)
        {
            Solution bestSolution = new Solution { StartIndex = 0, EndIndex = -1, Sum = 0 };
            decimal bestError = Math.Abs(target);
            Solution currentSolution = new Solution { StartIndex = 0, Sum = 0 };
    
            for (int i = 0; i < elements.Count; i++)
            {
                currentSolution.EndIndex = i;
                currentSolution.Sum += elements[i];
                while (elements[currentSolution.StartIndex] <= currentSolution.Sum - target)
                {
                    currentSolution.Sum -= elements[currentSolution.StartIndex];
                    ++currentSolution.StartIndex;
                }
                decimal currentError = Math.Abs(currentSolution.Sum - target);
                if (currentError < bestError || currentError == bestError && currentSolution.Length < bestSolution.Length )
                {
                    bestError = currentError;
                    bestSolution.Sum = currentSolution.Sum;
                    bestSolution.StartIndex = currentSolution.StartIndex;
                    bestSolution.EndIndex = currentSolution.EndIndex;
                }
            }
    
            return elements.GetRange(bestSolution.StartIndex, bestSolution.Length);
        }
    

    【讨论】:

    • “线性时间”...什么线性?不在输入集中的元素数量中。
    • 内存需求也不是 O(1),因为你必须存储一个列表。
    • 事实上,您的答案中唯一正确的部分是“动态编程”这两个词。哦,我明白了,你假设相邻元素的运行。我不知道问题是否实际上仅限于相邻的运行。
    • 用代码替换了草稿证明,如果 shardl 想要子序列,那么@Gabriel 有正确的答案
    • 我已经对此进行了测试,它似乎可以工作,但仅查看代码我无法确定这是否返回了元素最少的解决方案。
    【解决方案3】:

    我用一些老式的方式打字,但效果很好!

        bool InRange(decimal num, decimal value, decimal range)
        {
            return ((num >= value - range) && (num < value + range));
        }
    
        decimal GetClosestSum(decimal value, List<decimal> elements, decimal range)
        {
            elements.Sort();
            var possibleResults = new List<decimal>();
            for (int x = elements.Count - 1; x > 0; x--)
            {
                if (InRange(elements[x], value, range)) possibleResults.Add(elements[x]);
                decimal possibleResult = elements[x];
                for (int i = x - 1; i > -1; i--)
                {
                    possibleResult += elements[i];
                    if (possibleResult > (value + range - 1)) possibleResult -= elements[i];
                    if (InRange(possibleResult, value, range)) possibleResults.Add(possibleResult);
                }
            }
            decimal bestResult = -1;
            for (int x = 0; x < possibleResults.Count; x++)
            {
                if (bestResult == -1)
                    bestResult = possibleResults[x];
                if (Math.Abs(value - possibleResults[x]) < Math.Abs(value - bestResult))
                    bestResult = possibleResults[x];
            }
            return bestResult;
        }
    

    【讨论】:

      猜你喜欢
      • 2014-06-12
      • 2013-04-07
      • 1970-01-01
      • 1970-01-01
      • 2017-12-06
      • 1970-01-01
      • 2021-05-25
      • 2012-12-27
      • 1970-01-01
      相关资源
      最近更新 更多