【问题标题】:Sum of Array's Subsets数组子集的总和
【发布时间】:2015-09-15 16:05:37
【问题描述】:

我正在寻找有关查找数组子集的帮助。

int[] array = { 1,2,3,5,8,10,15,23};

我必须找到一个数组的所有子集。如果子集元素的总和等于数组中的任何数字,那么我的计数器就会增加。例如:1+2=3、2+3=5、5+8+10=23、1+2+5=8、2+3+8+10=23

public static void Main(string[] args)
    {
        int[] array = { 1, 2, 3, 5, 8, 10, 15, 23 };
        int arrayLength = array.Count();
        int sum = 0;
        int subsetCount = 0;

        for (int i = 0; i < arrayLength; i++)
        {
            for (int j = i + 1; j < arrayLength; j++)
            {
                sum = array[i] + array[j];

                for (int m = j + 1; m < arrayLength; m++)
                {
                    for (int k = 0; k < arrayLength; k++)
                    {
                        if (array[k] == sum)
                        {
                            subsetCount++;
                        }
                    }
                    sum = array[i] + array[j] + array[m];
                }
            }
        }
        Console.WriteLine(subsetCount);
        Console.ReadLine();
    }

我可以接受 2 元素和 3 元素的子集。但是4及以上我不知道怎么解决?

任何帮助将不胜感激

【问题讨论】:

  • 我建议做的一件事是编写一个方法来检查子集元素的总和是否等于数组中的任何数字。所以得到一个总和,然后将其传递给该方法,让该方法返回 true 或 false,如果为 true,则增加您的计数器。
  • 我认为部分答案可以很好地复制 - How to get all subsets of an array,但这可能太难从那个跳到完整答案...
  • 致 OP:请注意,如果您正在处理真正的子集(即不是您的答案所关注的连续子集),评论者 @AlexeiLevenkov 引用的问题包括描述高效、位数组的答案- 寻找子集的解决方案。从内存占用的角度来看,这些方法比我描述的递归方法更好,但随着初始数组长度的增加,它们也会有类似的时间限制。如果合适,这两种解决方案都值得熟悉。如果这解决了您的问题,请考虑将此问题作为该问题的副本结束。

标签: c# arrays sum subset


【解决方案1】:

您只需要两个循环即可找到所有子集的总和。外循环是子集的起点,内循环是从该起点计算所有子集的总和。

以第一个索引为起点,子集为1+21+2+31+2+3+5 等。由于您只对子集的总和感兴趣,因此您只需将一项添加到另一项即可获得子集的总和。

然后对于每个 sum 循环遍历项目以检查匹配:

int[] array = { 1, 2, 3, 5, 8, 10, 15, 23 };
int subsetCount = 0;
for (int i = 0; i < array.Length; i++) {
  int sum = array[i];
  for (int j = i + 1; j < array.Length; j++) {
    sum += array[j];
    for (int k = 0; k < array.Length; k++) {
      if (array[k] == sum) {
        subsetCount++;
      }
    }
  }
}
Console.WriteLine(subsetCount);

编辑:

我假设您的意思是连续子集,但从您的示例看来,您还想要非连续子集。

让我们从正确的解决方案开始:

23 = 15+8, 15+5+3, 15+5+2+1, 10+8+5, 10+8+3+2
15 = 10+5, 10+3+2, 8+5+2
10 = 8+2, 5+3+2
 8 = 5+3, 5+2+1
 5 = 3+2
 3 = 2+1

这为我们提供了 14 个不同的子集,它们总和为集合中的一个项目。

您可以递归地计算子集,只跟踪子集中项目的总和和数量。您不需要实际的子集,只需知道总和以及子集中至少有两个项目。

集合中的子集是与集合其余部分中的所有子集以及集合其余部分中的子集相结合的第一项。例如[1,2,3] 的子集s()1,s([2,3])s([2,3])

这给了你:

public static int CountSubsets(int[] arr, int start, int len, int sum) {
  int cnt = 0;
  if (start < arr.Length) {
    if (len >= 1 && arr.Contains(sum + arr[start])) cnt++;
    cnt += CountSubsets(arr, start + 1, len + 1, sum + arr[start]);
    cnt += CountSubsets(arr, start + 1, len, sum);
  }
  return cnt;
}

并称它为:

int[] set = { 1, 2, 3, 5, 8, 10, 15, 23 };
Console.WriteLine(CountSubsets(set, 0, 0, 0));

输出:

14

【讨论】:

  • 感觉子集的数量比它应该的要小(接近 n^2 而不是 2^n),除非我在帖子中遗漏了一些东西(我没有看到“顺序”那里)。
  • 实际上,在阅读了几次之后,我也没有看到“所有适当的子集” - 看起来不错的解决方案 - OP 需要添加测试并完成..
  • @AlexeiLevenkov 哎呀。通过顺序总和,我的意思是索引。它只会检查1+21+2+31+2+3+5 而不是2+3+8+10,因为5 介于38 之间
  • 这只是选择数组的子部分,而不是子集。
  • @Enigmativity:你说得对,我假设是连续子集。我添加了一个包含非连续子集的解决方案。
【解决方案2】:

这对我来说似乎很像家庭作业。因此,我将本着这种精神来回答(即,与其编写代码,不如为您指明正确的方向)。

首先,您所说的“子集”并不是很清楚。您是在谈论数组中元素的连续运行吗?还是您的字面意思是将数组视为无序集,从中检查每个可能的子集?

后者明显比前者难。所以我暂时假设前者。

那么,看来你真的有两个不同的问题:

  • 找到数组的所有子集并对每个子集求和
  • 对于给定的总和,判断它是否在数组中

后者相当简单。如果您知道这些数组总是相对较短(希望它们会如此,否则“查找所有子集”可能需要一段时间 :)),您可以在每次要查找新总和时进行线性搜索。

另外,一种语义更直接的方法是使用数组的成员创建一个 HashSet&lt;int&gt; 实例,然后当您想知道总和是否在数组中时,只需检查您的集合。

例如:

HashSet<int> setOfValues = new HashSet<int>(array);

那么你可以写setOfValues.Contains(sum)来检查变量sum所持有的值是否包含在数组中。

至于第一个问题,在我看来你真的应该只需要两个循环:

  • 循环迭代子集的第一个元素的索引。 IE。您需要枚举所有子集,首先从所有以元素 0 开始的子集开始,然后从所有以元素 1 开始的子集开始,依此类推。
  • 循环迭代子集的长度。 IE。确定了当前子集组的起始索引后,现在您要查找所有实际子集:第一个子集的长度为 1,第二个的长度为 2,依此类推,直到最大可能长度(即数组的长度)减去当前从 0 开始的起始索引值)。


考虑一下另一种可能性——你将数组视为无序集——那么在我看来,一个明显的,如果是蛮力的方法,就是递归地生成子集。

在这种方法中,您将再次有一个循环来枚举子集长度,从 1 开始,直到原始集的总长度。然后,给定一个长度,您需要选择所有可能的子集。

您可以递归地执行此操作:

  • 给定一个集合,迭代当前集合的元素(传递给您的递归方法)。当前元素是当前子集的新元素。
  • 如果您当前的子集现在是正确的长度,请将其相加并与原始集进行比较。
  • 否则,从当前集合中移除当前元素并递归。

上述最简单的实现将为每个递归级别创建当前集合的新副本,以在它调用自身时传递给该方法。这样您就不必担心一个递归级别会干扰之前的级别。

请注意,这仅适用于相对较小的初始集。在您的计算机没有足够的时间或内存来完成对所有可能子集的搜索之前,不需要非常大的初始数组。

【讨论】:

    【解决方案3】:

    首先,您需要一个返回所有子集的方法。

    Func<IEnumerable<int>, IEnumerable<IEnumerable<int>>> getAllSubsets = null;
    getAllSubsets = xs =>
        (xs == null || !xs.Any())
            ? Enumerable.Empty<IEnumerable<int>>()
            :  xs.Skip(1).Any()
                ? getAllSubsets(xs.Skip(1))
                    .SelectMany(ys => new [] { ys, xs.Take(1).Concat(ys) })
                : new [] { Enumerable.Empty<int>(), xs.Take(1) };
    

    所以给定getAllSubsets(new[] { 1, 2, 3 }) 我明白了:

    {  } 
    { 1 } 
    { 2 } 
    { 1, 2 } 
    { 3 } 
    { 1, 3 } 
    { 2, 3 } 
    { 1, 2, 3 } 
    

    现在很容易计算出您想要的结果。

    int[] array = { 1,2,3,5,8,10,15,23};
    
    var result =
        getAllSubsets(array)
            .Where(ss => array.Contains(ss.Sum()))
            .Count();
    

    我收到22

    【讨论】:

    • 这是一个令人印象深刻的答案。递归 LINQ 不适合胆小的人。但我有两个担忧:1)如果 OP 真的想要真正的子集(这对我来说根本不清楚......问题中给出的例子可能是指示性的,或者可能只是错别字),骗子阿列克谢指出回答了这个问题,并且 2) 以 LINQ 为中心的方法可能会完全混淆任何理解 OP 实际上将从这里受益的东西。
    • @PeterDuniho - 谢谢。我确实喜欢使用 LINQ 方法,即使它有点麻烦,因为一旦编码和验证,它将数据的生成与查询和过滤分开。如果您确定子集部分有效,它只会使代码更容易理解。 for 循环方法通常很难验证正确性,我认为它实际上更容易混淆。我觉得将毛茸茸的逻辑封装到单个递归 LINQ 语句中会更好。
    【解决方案4】:

    使用一点 Linq:

    int[] array = {1, 2, 3, 5, 8, 10, 15, 23};
    var subsets = new List<IEnumerable<int>>();
    int counter = 0;
    
    for (int i = 0; i < array.Length; i++)
    {
        for (int j = 2; j < array.Length - i; j++)
        {
            if (array.Contains(array.Skip(i).Take(j).ToList().Sum()))
            {
                counter++;
            }
        }
    }
    
    Console.WriteLine("Number of subsets:" + counter);
    

    给你:

    子集数:5

    【讨论】:

    • 如果您要使用 LINQ,为什么还要物化子集列表呢?为什么不直接在Take() 之后调用Sum() 并为自己节省所有这些分配?
    • 我想保留子集,以便将其添加到列表中。如果我立即调用 Sum(),我想我会失去成分。
    • @openshac 但 OP 的作业只要求计数...(至少您应该描述您要回答的问题)
    • 这只是选择数组的子部分,而不是子集。
    • @openshac - 您没有选择子集 - 仅选择部分。 OP的问题清楚地显示了子集,即1+2+5=8, 2+3+8+10=23
    猜你喜欢
    • 1970-01-01
    • 2017-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-29
    • 1970-01-01
    相关资源
    最近更新 更多