【问题标题】:Given two sets of numbers, find the smallest set from each where the sum is equal给定两组数字,找出总和相等的最小集合
【发布时间】:2012-10-26 23:03:51
【问题描述】:

我正在开发一个应用程序,该应用程序需要根据各种标准匹配两组数据,包括每组中任意数量项目的总和。我已将问题提炼为以下陈述:

给定一组项目和交易,找到总和等于最小交易集总和的最小项目集。 (本文忽略了一些复杂性,但现在我只关心匹配的总金额,而不是日期、描述、清算差异等)

或者,在数学上:给定两组数字,从每组中找出总和相等的最小集合。

我遇到的其他类似 SO 问题假设您提前知道总和,或者知道您要购买的每组的数量。

这是一个测试,(我认为)说明了我的目标。

    [TestMethod]
    public void StackOverflowTest()
    {
        var seta = new[]{10, 20, 30, 40, 50};
        var setb = new[]{ 45, 45, 100, 200 };

        var result = Magic(seta, setb);


        Assert.AreEqual(new[]{40,50},result.SetA);
        Assert.AreEqual(new[] { 45, 45 }, result.SetB);
    }
    class MagicResult
    {
        public int[] SetA { get; set; }
        public int[] SetB { get; set; }

    }
    private MagicResult Magic(int[] seta, int[] setb)
    {
        throw new NotImplementedException();
    }

我正在寻找一个优雅的解决方案来通过这个,但会接受任何让我到达那里的伪代码或建议;)

【问题讨论】:

  • +1 用于包含测试方法:D
  • 如果有多个集合满足此条件,您会怎么做?另外,你想要最小的集合和最小的数字吗?
  • 最后一个 :) - 一组 1 个可以接受吗?
  • @Abe:“最小集合”是指项目数量最少的集合。因此,如果有多个满足条件的集合,那么它应该返回具有最少项目数的集合。如果多个匹配该条件,那么它可以只返回第一个匹配项(在实际应用中,这不太可能)。
  • A 和 B 中的一组 1 是可以接受的。一组来自 A 的 1 和来自 B 的 0 是不可接受的。因此,如果测试中的 SetA 和 SetB 都包含“42”,那么它将返回 setA 中的 42 和 setB 中的 42 的结果。

标签: c# algorithm set


【解决方案1】:

蛮力:

 var result = (from a in seta.Subsets()
               from b in setb.Subsets()
               where a.Count() > 0 && b.Count() > 0
               where a.Sum() == b.Sum()
               orderby a.Count() + b.Count()
               select new MagicResult { SetA = a.ToArray(), SetB = b.ToArray() }
              ).First();

使用来自EvenMoreLINQ project 的子集方法。

【讨论】:

  • @L.B:如果条件为真的最小集是完整集,你真的可以进行非穷举搜索吗?
  • Dtb,我想不出更好的算法,但这并不意味着不能使用修剪来设置更好的算法。你的答案是最简单的。
  • FWIW - 运行时间为O(2^n)
  • 这段代码比O(2^n)差。每组大小n 都有2^n 子集,所以有2^n * 2^n = 2^(2n) 比较!
  • 对!所以它可能适用于非常小的集合,但很快就会变得丑陋。
【解决方案2】:

这可以在O(nW) 时间使用动态规划来解决,其中 W 是最大和的大小。求解两个集合的knapsack problem,为每个集合生成一个数组,其中包含所有可能的总和并跟踪使用的项目数。然后,比较每个数组中相等的和以找到每个数组的最小值

未经测试,但这是这个想法。

arr1dp = [None]*W;  arr1dp[0] = 0;
arr2dp = [None]*W;  arr2dp[0] = 0;


# knapsack arr1
for i in range(len(arr1)):
    for cur_item in arr1:
        if (arr1dp[cur_item] is not none):
             arr1dp[cur_item+i] = min(arr1dp[cur_item]+1,arr1dp[cur_item])

# do the same for arr2
# omitted for brevity

# find the smallest match
for i in range(W):
    if arr1dp[i] is not none and arr2dp[i] is not none:
         min_val = min(min_val,arr1dp[i]+arr2dp[i])

【讨论】:

    【解决方案3】:

    如果两个集合包含一个共同的数字,则有一个大小为 1 的解。

    如果不是,请尝试所有两个数字的总和(每组中有 N-choose-two 或 N*(N-1)/2)。将它们与单数和双数和的集合进行比较。

    如果不满意,请尝试所有三个数字的总和,将它们与 1、2 或 3 数字的总和进行比较;以此类推,直到尝试了所有总和(对于一组大小为 N 的 2**N)。

    这里的工作代码一旦找到解决方案就会停止搜索。 (相同数量的和数可能会更小)。它在 python 中,但这实际上是伪代码:-)

    from itertools import combinations
    
    # To allow lists of different sizes: ensure list1 is never the short one
    if len(list1) < len(list2):
        list1, list2 = list2, list1
    
    def found(val, dict1, dict2):
        print "Sum:", val
        print "Sum 1", dict1[val]
        print "Sum 2", dict2[val]
    
    def findsum(list1, list2):
        # Each dict has sums as keys and lists of summands as values.
        # We start with length 1:
        dict1 = dict()
        dict2 = dict()
    
        for n in range(1, max(len(list1), len(list2))+1):
            # Check all size n sums from list1 against size < n sums in list2
            for nums in combinations(list1, n):
                s = sum(nums)
                if s in dict1:  # Is this sum new for our list?
                    continue
    
                dict1[s] = nums
                if s in dict2:   
                    found(s, dict1, dict2)
                    return   # If you want to look for a smallest sum, keep going
    
            # If list2 is too short, nothing to do
            if len(list2) < n:
                continue
    
            # Check all size n sums from list2 against size <= n sums in list1
            for nums in combinations(list2, n):
                s = sum(nums)
                if s in dict2:  # Is this sum new for our list?
                    continue
    
                dict2[s] = nums
                if s in dict1:
                    found(s, dict1, dict2)
                    return   # If you want to look for a smallest sum, keep going
    
    findsum(list1, list2)
    

    这旨在以最少的比较次数找到解决方案。如果您还希望总和最小,则在每个大小 n 一次生成所有 n 部分总和,对它们进行排序并按升序检查它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多