给定两组数字,找到总和相等的每一组的最小集合

时间:2012-10-26 23:03:52

标签: c# algorithm set

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

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

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

我遇到的其他类似的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();
    }

我正在寻找一个优雅的解决方案,这将使这个通过,但将采取任何伪代码或建议,让我在那里;)

3 个答案:

答案 0 :(得分:3)

蛮力:

 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中的子集方法。

答案 1 :(得分: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])

答案 2 :(得分:1)

如果这两个集合包含一个共同的数字,则有一个大小为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部分总和,对它们进行排序并按递增顺序检查它们。