挑选五个总和为S的数字

时间:2010-09-10 11:29:42

标签: algorithm optimization hash

给定AN个非负数的数组,我有兴趣找到可以选择5个数字的方式(从数组中的不同位置),使得它们的总和为{{ 1}}。

S中有一个简单的解决方案:

O(N^3)

其中Let H be a hash table of (sum, position of leftmost element of sum) for i = 0, N for j = i + 1, N H.add(A[i] + A[j], i) numPossibilities = 0 for i = 0, N for j = i + 1, N for k = j + 1, N numPossibilities += H.get(S - (A[i] + A[j] + A[k]), k) 返回散列中元素的数量,其总和与H.get(x, y)具有相同的散列且其最左边的元素大于k。

或者,我们可以将3个元素的总和添加到哈希表中,然后继续使用2个嵌套for循环。然而,复杂性仍然相同,我们只是使用更多内存。

假设输入相当随机(因此没有最坏情况哈希),是否有一种算法可以在xO(N^2)中解决此问题,如果是O(N^2 log N)在所有情况下都持有?我认为二进制搜索可能有所帮助,但我不知道如何处理重叠索引。

上述解决方案在实践中比天真的5-for-loops解决方案好很多,但是我觉得我们可以做得更好,因此这个问题。

如果你能证明不存在这样的算法,那么如何优化上述解决方案?

澄清:

在最坏的情况下,上述算法确实是O(N^3),例如当给定数组只包含数字1时,我们有O(N^5)。但是,平均而言,S = 5方法更接近H.get,因此我的平均立方复杂度。

如果你实现这个并在一个很大的间隔(比如0到Int32.MaxValue)中的1000个随机数上运行它,你会看到它运行得相对较快。不过,找到需要很长时间的输入并不难。即使我们无法让所有相同数字的运行速度足够快,我们可以进行哪些优化?

在相同的假设下,我们可以做得更好,渐近或至少在实践中做得好吗?

4 个答案:

答案 0 :(得分:11)

答案 1 :(得分:4)

O(N ^ 3)似乎是可能的(尽管我还没有尝试过证明)。

取所有可能的对并创建一个大小为O(N ^ 2)的新数组(比如说B),它保存所有可能对的总和。还要跟踪原始数组中给出该总和的两个元素的索引。 - O(N ^ 2)

现在对数组进行排序 - O(N ^ 2LogN)。

现在对于原始数组中的每个元素a,尝试从B中找到两个与S-a相加的元素。由于B被排序,这可以在O(B)时间内完成:从两个指针开始,一个在最大值,一个在最小值。

如果那两个的总和> S-a,将指针递减到最大值。

如果那两个的总和< S-a,将指针递增到min附近。

如果总和相等,那么您已找到一个候选对和一个新的已排序子数组,可在其中查找下一个可能的候选对。 (你应该确保B的两个元素来自A的四个元素)。 (这里可能存在潜在问题)

因此,您可以将S-a的出现次数计算为B的两个元素之和,它来自原始数组的四个元素(不包括a)。

因此O(N ^ 2)时间为O(N)元素 - O(N ^ 3)。

希望有所帮助。

答案 2 :(得分:3)

您可以使用动态编程在O(N * S)中执行此操作:

static int count(int[] A, int S) {
    final int K = 5;
    // In count[n][s] we'll count the number of ways you can pick n numbers such that their sum is s
    int[][] count = new int[K+1][S+1];

    count[0][0] = 1;  // The base case
    for (int i = 0; i < A.length; i++)
        for (int n = K; n >= 1; n--)
            for (int s = A[i]; s <= S; s++)
                count[n][s] += count[n-1][s - A[i]];

    return count[K][S];
}

答案 3 :(得分:1)

也许最好先创建一个只包含不同值的数组,然后计算它们在原始数组中的出现次数。因为只需要解决方案的数量而不是解决方案本身,如果使用组合计算,这可能会更快。

1)排序数组A O(N log N)

2)创建一个新数组B,其中所有值都是不同的。    同时为A中的每个元素保存原始数组B中值的出现次数。 O(N)

3)创建一个新数组C,其中包含两个B元素的总和。    如果计数> 1,则包括相同元素的总和。 1。    同时保存B元素的两个索引。 O(| B | 2

4)将数组C除以总和O(| B | 2 (log | B | 2 ))

5)B中的每个元素都会找到C中的两个有效元素,以便这三个值总和为S    并且索引的顺序相同。在伪代码中:

num=0
for (i=0; i<n; i++)
  j=i
  k=|C|-1
  while (j <= k)
    if (c[j].sum + c[k].sum = S - b[i].value)
      for (m=0; m<c[j].index.length; m++)
        for (n=0; n<c[k].index.length; n++)
          if (i < c[j].index[m].left < c[j].index[m].right < c[j].index[k].left < c[j].index[k].right)
            num+=b[i].count * b[c[j].index[m].left].count * b[c[j].index[m].right].count * b[c[j].index[k].left].count * b[c[j].index[k].right].count
          else if (b[i].count > 1 && i = c[j].index[m].left < c[j].index[m].right < c[j].index[k].left < c[j].index[k].right)
            num+= binomialcoefficient(b[i].count, 2) * b[c[j].index[m].right].count * b[c[j].index[k].left].count * b[c[j].index[k].right].count
          else if (b[c[j].index[m].left].count > 1 && i < c[j].index[m].left = c[j].index[m].right < c[j].index[k].left < c[j].index[k].right)
            num+= b[i].count * binomialcoefficient(b[c[j].index[m].left].count, 2) * b[c[j].index[k].left].count * b[c[j].index[k].right].count
          [..]
          else if (b[i].count > 2 && i = c[j].index[m].left = c[j].index[m].right < c[j].index[k].left < c[j].index[k].right)
            num+= binomialcoefficient(b[i].count, 3) * b[c[j].index[k].left].count * b[c[j].index[k].right].count
          [..]
          else if (b[i].count > 1 && b[c[j].index[m].right].count > 1 && i = c[j].index[m].left < c[j].index[m].right = c[j].index[k].left < c[j].index[k].right)
            num+= binomialcoefficient(b[i].count, 2) * binomialcoefficient(b[c[j].index[m].right].count, 2) * b[c[j].index[k].right].count
          [..]
          else if (b[i].count > 4 && i = c[j].index[m].left = c[j].index[m].right = c[j].index[k].left = c[j].index[k].right)
            num+= binomialcoefficient(b[i].count, 5)
    if (c[j].sum + c[k].sum >= S - b[i].value)
      k--
    if (c[j].sum + c[k].sum <= S - b[i].value)
      j++

我不确定它的时间复杂程度。外部for循环由O(| B |)绑定,while循环由O(| B | 2 )绑定,内部for循环由O(| B |)绑定,因为{{1}只有不同的值。    所以很明显它在O(| B | 5 )中。但是如果B中的所有元素具有相同的值并且如果所有值都是不同且足够随机的话,它的O(N)可以将A中每个和的索引数限制为常数,其中会导致O(N 3 )。

最坏的情况可能是某些地方的价值相等,而另一半是随机的或所有数字都不同但有很多重复的总和。但这也会导致更短的while循环。我有一种感觉,while和两个内部for循环都被O(N 2 )绑定,所以O(N 3 )总共适用于所有情况,但我不能证明它。

这里还有一个有趣的问题是,获取5个数字的最大可能性是多少,对于N个分解数字的数组,这些数字总和为S.如果它在O(N 5 )中,该算法的最坏情况也是O(N 5 )。

也许尝试一下;)。