【问题标题】:Given an array A of size N, find all combination of four elements in the array whose sum is equal to a given value K给定一个大小为 N 的数组 A,找到数组中所有四个元素的组合,其和等于给定值 K
【发布时间】:2020-04-17 16:25:39
【问题描述】:

给定一个大小为 N 的数组 A,找出数组中四个元素的所有组合,其和等于给定值 K。例如,如果给定数组是 {10,2,3,4,5,9 , 7, 8} 和 K = 23,其中一个是“3 5 7 8”(3 + 5 + 7 + 8 = 23)。

输出应该只包含唯一的四元组 例如,如果输入数组是 {1, 1, 1, 1, 1, 1} 并且 K = 4,那么输出应该只有一个四元组 {1, 1, 1 , 1}

我的方法:我试图通过将给定数组形成的所有不同对存储到哈希表 (std::unordered_multimap) 中来解决这个问题,并将它们的总和作为键。然后对于每一对总和,我在哈希表中查找 (K - sum) 键。这种方法的问题是我得到了太多重复,比如 (i, j, l, m) 和 (i, l, j, m) 是相同的,而且由于数组中的相同项目而存在重复。我不确定解决这个问题的最佳方法是什么。

上述方法的代码为:

#include <iostream>
#include <unordered_map>
#include <tuple>
#include <vector>


int main() {
    size_t tc = 0;
    std::cin >> tc; //number of test cases
    while(tc--) {
        size_t n = 0, k = 0;
        std::cin >> n >> k;
        std::vector<size_t> vec(n);
        for (size_t i = 0; i < n; ++i)
            std::cin >> vec[i];

        std::unordered_multimap<size_t, std::tuple<size_t, size_t>> m;
        for (size_t i = 0; i < n - 1; ++i)
        for (size_t j = i + 1; j < n; ++j) {
            const auto sum = vec[i] + vec[j];
            m.emplace(sum, std::make_tuple(i, j));
        }

        for (size_t i = 0; i < n - 1; ++i)
        for (size_t j = i + 1; j < n; ++j) {
            const auto sum = vec[i] + vec[j];
            auto r = m.equal_range(k - sum);
            for (auto it = r.first; it != r.second; ++it) {
                if ((i == std::get<0>(it->second))
                ||(i == std::get<1>(it->second))
                ||(j == std::get<0>(it->second))
                || (j == std::get<1>(it->second)))
                continue;
                std::cout << vec[i] << ' ' << vec[j] << ' ' 
                << vec[std::get<0>(it->second)] << ' '
                << vec[std::get<1>(it->second)] << '$';
            }

            r = m.equal_range(sum);
            for (auto it = r.first; it != r.second; ++it) {
                if ((i == std::get<0>(it->second))
                && (j == std::get<1>(it->second))) {
                    m.erase(it);
                    break;
                }
            }
        }

        std::cout << '\n';
    }
    return 0;
}

上面的代码将在注释中下面提到的链接中按原样运行。

注意:本题取自https://practice.geeksforgeeks.org/problems/find-all-four-sum-numbers/0

【问题讨论】:

  • 这是具有子集大小限制的“目标总和”问题,并且没有重复的解决方案。请在此处发布之前研究发布在 Stack Overflow 和其他地方的解决方案;避免重复也包含在本组中。
  • 查看一个通用解决方案here

标签: arrays algorithm hashmap


【解决方案1】:

处理数组中的重复值

考虑[2, 2, 2, 3, 3] 与目标10

唯一的解决方案是4-tuple&lt;2,2,3,3&gt;。重点是避免在三个2中选择两个2。

让我们考虑k-class,其中每个元组仅包含k 的元组集合。

例如:在我们的数组中,我们有 2-class3-class2-class 包含:

<2>
<2,2>
<2,2,2>

3-class 包含:

<3>
<3,3>

一个想法是将elem 数组(elem 是一个整数值)减少为k-class 数组。 同理

[[<2>, <2,2>, <2,2,2>], [<3>, <3,3>]]

我们可以考虑在2-class 集合和3-class 集合之间取笛卡尔积,并检查哪个结果导致解。

更具体地说,让我们取一些元组T,其最后一个(==最右边的)值为k。 (在&lt;2,3,4&gt; 中,最右边的值为4)。

我们可以从我们的数组 (* l &gt; k) 中选择任何 l-class 并将元组从该 l-class 连接到 T

例如

  • 考虑数组[2, 9, 9, 3, 3, 4, 6] 和元组&lt;2, 3, 3&gt;
  • 最右边的值为3
  • 候选人k-class4-class6-class9-class

我们可以加入:

<4>
<6>
<9>
<9,9>

所以下一个候选人将是:

<2, 3, 3, 4>
<2, 3, 3, 6>
<2, 3, 3, 9>
<2, 3, 3, 9, 9> //that one has too many elem, left for illustration

(* l &gt; k 的目的是防止排列。(如果 &lt;1,2&gt; 是您不想要的解决方案 &lt;2,1&gt; 因为加法是可交换的))

算法

Foreach tuple, try to create new ones by rightjoining tuples from a "greater" k-class.
discard the resulting ones which have too many elements or whose sum is already too big...
At some point we won't have new candidates, so algorithm will stop

剪切示例:

  1. 给定数组[2,3,7,8,10,11],元组&lt;2,3&gt;S == 13

    • &lt;2,3,7&gt; 是候选者 (2+3+7 = 12
    • &lt;2,3,10&gt;不是候选者(2+3+10 = 15 > 13)
    • &lt;2,3,11&gt; 更是如此。 不是
    • &lt;2,3,8&gt; 也不是候选者,因为下一个右连接(到达4-tuple)将溢出S
  2. 给定数组[2,3,4,4,4]给定元组&lt;2,3&gt;和候选&lt;4,4,4&gt;

结果元组将是&lt;2,3,4,4,4&gt;,它有太多元素,丢弃它!

显然初始化是

  • 一些空元组
  • 总和为0
  • 并且其最右边的元素小于数组中的任何k(您可以将其右加入任何人)

我相信翻译成C++应该不会太难

class TupleClass {
  sum = 0
  rightIdx = -1
  values = [] // an array of integers (hopefully summing to solution)

  //idx is the position of the k-class found in array
  add (val, idx) {
    const t = new TupleClass()
    t.values = this.values.concat(val)
    t.sum = this.sum + val
    t.rightIdx = idx

    return t;
  }

  toString () {
    return `<${this.values.join(',')}>`
  }

  addTuple (tuple, idx) {
    const t = new TupleClass
    t.values = this.values.concat(tuple.values)
    t.sum = this.sum + tuple.sum
    t.rightIdx = idx

    return t;
  }
  get size () {
    return this.values.length
  }
}

function nodupes (v, S) {

  v = v.reduce((acc, klass) => {
    acc[klass] = (acc[klass] || {duplicity: 0, klass})
    acc[klass].duplicity++
    return acc
  }, {})
  v = Object.values(v).sort((a,b) => a.klass - b.klass).map(({ klass, duplicity }, i) => {
    return Array(duplicity).fill(0).reduce((acc, _) => {
      const t = acc[acc.length-1].add(klass, i)
      acc.push(t)
      return acc
    }, [new TupleClass()]).slice(1)
  })
  //v is sorted by k-class asc
  //each k-class is an array of tuples with increasing length
  //[[<2>, <2,2>, <2,2,2>], [<3>,<3,3>]]
  let tuples = [new TupleClass()]
  const N = v.length

  let nextTuples = []

  const solutions = []
  while (tuples.length) {
    tuples.forEach(tuple => {
      //foreach kclass after our rightmost value
      for (let j = tuple.rightIdx + 1; j <= N - 1; ++j) {

        //foreach tuple of that kclass
        for (let tclass of v[j]) {
          const nextTuple = tuple.addTuple(tclass, j)

          if (nextTuple.sum > S || nextTuple.size > 4) {
            break
          }

          //candidate to solution
          if (nextTuple.size == 4) {
            if (nextTuple.sum === S) {
              solutions.push(nextTuple)
            }
            //invalid sum so adding more elem won't help, do not push
          } else {
            nextTuples.push(nextTuple)
          }
          
        }
      }
    })
    tuples = nextTuples
    nextTuples = []
  }
  return solutions;
}
const v = [1,1,1,1,1,2,2,2,3,3,3,4,0,0]
const S = 7
console.log('v:', v.join(','), 'and S:',S)
console.log(nodupes(v, 7).map(t=>t.toString()).join('\n'))

【讨论】:

  • 感谢分享解决方案,我需要通过您的代码才能更好地理解它。同时你能告诉我这个解决方案的时间复杂度是多少。我正在寻找使用哈希表的解决方案O(n^2)
  • 那将是 O(k^4) 其中 k 是类的数量。 (C_k^4)。
  • 考虑到 4 可以分解为 1+1+1+1, 1+1+2, 1+3, 2+2 即计算,一个轻微的优化是缓存一些总和所有1+1-tuple(来自两个不同类的1-tuple),然后将它们自己右加入(这使得1 + 1 + 1 + 1 总和)。也可以将它用于 1+1+2(我们已经计算了每个 k 类的 2 元组)。但我还没有计时所以要小心
  • 并且可能会引用您的哈希表,而不是将元组存储在数组中,最好考虑使用多重映射:键是总和,值是关联的元组。因为我们将键(左元组)关联的值与与 S 键右元组关联的值交叉),我们可以通过强制执行 leftTuple.rightMost &lt; rightTuple.leftMost 来打破排列
猜你喜欢
  • 2019-06-04
  • 1970-01-01
  • 1970-01-01
  • 2016-03-12
  • 1970-01-01
  • 2013-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多