【问题标题】:Selecting items from a list to achieve a sum从列表中选择项目以获得总和
【发布时间】:2010-09-20 10:42:10
【问题描述】:

我有一个包含数值的项目列表,我需要使用这些项目求和。我需要你的帮助来构建这样一个算法。下面,有一个用 C# 编写的描述我的问题的示例:

int sum = 21;

List<Item> list = new List<Item>();
list.Add(new Item() { Id = Guid.NewGuid(), Value = 3 });
list.Add(new Item() { Id = Guid.NewGuid(), Value = 5 });
list.Add(new Item() { Id = Guid.NewGuid(), Value = 12 });
list.Add(new Item() { Id = Guid.NewGuid(), Value = 3 });
list.Add(new Item() { Id = Guid.NewGuid(), Value = 2 });
list.Add(new Item() { Id = Guid.NewGuid(), Value = 7 });

List<Item> result = // the items in the list that has the defined sum.

注意:我对结果中的项目数量没有限制。

【问题讨论】:

  • 这是 NP 完全问题:称为子集和问题。 en.wikipedia.org/wiki/Subset_sum_problem 。不幸的是,您不会找到任何 简单 解决方案 - 迄今为止发现的最佳解决方案在 O(2 ^ (N/2)) 中运行。
  • 这些值是否需要是可用的最大值?
  • 作业标签丢失了吗?
  • 谢谢@Ani。我认为你是对的。这很有帮助。
  • @Musa Hafalir,我认为你不知道一些限制,如果你真的没有,那么我们寻找 x % item[i] == 0 然后添加这个项目直到我们有那个总和(x)。所以我认为每个值只能使用一次,这是一个约束。 ;-)

标签: c# .net algorithm list combinations


【解决方案1】:

这被称为 Subset sum problem,被认为是计算机科学中的一个难题。不是很难做到,而是很难做到——你可以很容易地编写一个算法来做到这一点,但对于相当大的输入,它很容易需要数十亿年的时间。

如果您对仅适用于少量输入的慢速解决方案感到满意,请尝试以下方法:

  • 生成输入列表的所有子集。

  • 对于每个子集,计算该子集中项目的总和。

  • 返回总和匹配的第一个子集。

这是一个返回所有子集的方法(实际上是 subsequences,因为它维护项目的顺序,尽管在您的情况下这没有区别):

/// <summary>
/// Returns all subsequences of the input <see cref="IEnumerable&lt;T&gt;"/>.
/// </summary>
/// <param name="source">The sequence of items to generate
/// subsequences of.</param>
/// <returns>A collection containing all subsequences of the input
/// <see cref="IEnumerable&lt;T&gt;"/>.</returns>
public static IEnumerable<IEnumerable<T>> Subsequences<T>(
        this IEnumerable<T> source)
{
    if (source == null)
        throw new ArgumentNullException("source");
    // Ensure that the source IEnumerable is evaluated only once
    return subsequences(source.ToArray());
}

private static IEnumerable<IEnumerable<T>> subsequences<T>(IEnumerable<T> source)
{
    if (source.Any())
    {
        foreach (var comb in subsequences(source.Skip(1)))
        {
            yield return comb;
            yield return source.Take(1).Concat(comb);
        }
    }
    else
    {
        yield return Enumerable.Empty<T>();
    }
}

所以你现在可以写这样的东西了...

var result = list.Subsequences()
                 .FirstOrDefault(ss => ss.Sum(item => item.Value) == sum);

【讨论】:

  • 如果您计算一个接近您需要的总和的子集,在大多数情况下会不会更快,而不是查看是否有任何 1/2/3 元素恰好适合您的边距还需要吗?假设您有大量数字并且知道这些数字有多大?我问这个纯粹是出于兴趣:)
  • 也许我们应该首先删除价值大于总和的元素 :) 我仍然需要一些优化,但答案很容易解释。谢谢。
  • @Samuel, @Musa:你们俩都提出了可能的优化方案。再多一些,您可能正在走向计算机科学研究!如果你能证明你的新算法比 O(2^(n/2)) 具有更好的复杂性,并在同行评审的论文中发表这个证明,你就会出名:)
【解决方案2】:

这被称为子集和问题,经过修改 - 您不想达到零,而是达到特定的数字。

这是 Wiki 对此的评价 - http://en.wikipedia.org/wiki/Subset_sum_problem

您可能会根据您对领域的了解想到一些优化。例如,如果最大数字 + 最小数字大于总和 -> 永远不会使用最大数字,您可以将其排除(并对新的最大数字尝试相同的操作..)。

我记得按照 Samuel 的建议进行操作 - 递归方式,并没有那么糟糕,(但当然总是存在 stackoverflow 问题...)。

【讨论】:

    【解决方案3】:

    递归,添加元素直到A)你达到总和或B)你得到太多,如果A你完成了,如果B你改变元素尝试所有可能的配置。如果当前元素已经大于超过总和的最后一个元素,可能会禁止系统添加元素

    【讨论】:

      【解决方案4】:

      我不完全确定您在这里是在寻找哪个太阳。如果您想要所有值的总和,请使用以下代码:

      int result = list.Sum( i => i.Value);
      

      如果您想要所有具有特定值的元素,请使用以下代码:

      int x = 3;
      List<Item> result = list.Where( i => i.Value == x);
      

      【讨论】:

      • 应该是list.Where( i =&gt; i.Value == x);(相等的双等号)
      • @Benny Jobigan 但这不是提问者甚至想要的
      • 抱歉这里的拼写错误。我现在已经修好了。
      • @colithium 是的,我现在看到解决方案有所不同。很难理解 OP 想要什么。无论如何,我只是指出代码中的一个错字(尽管我怀疑它会编译)。
      猜你喜欢
      • 2016-03-27
      • 2016-10-26
      • 2016-08-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-04
      相关资源
      最近更新 更多