9

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

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

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

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

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

4

3 回答 3

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 项目中的 Subsets 方法。

于 2012-10-26T23:19:36.553 回答
2

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

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

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])
于 2012-10-26T23:56:48.707 回答
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 部分总和,对它们进行排序并按升序检查它们。

于 2012-10-26T23:19:53.213 回答