为什么我的子集和方法不正确?

Why is my approach to subset sum incorrect?

本文关键字:不正确 方法 我的 子集 为什么      更新时间:2023-10-16

我是动态编程的新手,并提出了我的(显然不正确的(子集和问题的方法。我想知道为什么我的方法不正确。特别是我很好奇基本的想法是否有效,或者我是否应该坚持使用子集和的正常方法,请参阅 yt。

问题:给定一个数字数组,在数组中找到 2 个子集,这两个子集的总和相同。与正常的子集和问题相比,这个问题略有变化。

示例:[1,5,5,9]可以分为[1,9][5,5]

想法:

1  5  5  9
0  5  5  9
1  1  6  6  10
5  6  5  6  10
5  6  10 10 10
9  6  10 10 10

与其跟踪我采用哪些元素,哪些元素不(像往常一样(,我想跟踪总和。这个想法是在mem[i-1][j](在当前位置上方一个(找到先前元素的总和。如果该值 + 当前值小于或等于总和的一半(在本例中为 20(,我们将当前值添加到总和中。否则,我们只是取以前的值而忽略当前值。

表格中对角线上的元素将只是其自身。我这样做是因为否则我会添加相同的元素两次。

在示例中,算法将在看到前 10 个时终止。

实现:

玩转代码

bool has_solution(std::vector<int> &v) {
const long long sum = accumulate(v.begin(), v.end(), 0);
long long mem[v.size() + 1][v.size()];
for (int j = 0; j < v.size(); ++j) {
mem[0][j] = v.at(j);
}
mem[0][0] = 0;
for(int i = 1; i < v.size(); ++i) {
for (int j = 0; j < v.size(); ++j) {
if (i - 1 == j) {
mem[i][j] = v.at(i - 1);
} else {
const long long new_sum = mem[i - 1][j] + v.at(i - 1) ;
if (new_sum <= sum - new_sum) {
mem[i][j] = new_sum;
} else {
mem[i][j] = mem[i - 1][j];
}
}
if (mem[i][j] * 2 == sum) {
return true;
}
}
}
return false;
}

该算法为输入提供了不正确的解决方案[987, 856, 743, 491, 227, 365, 859, 936, 432, 551, 437, 228, 275, 407, 474].根据站点,它应该返回 true,但它返回 false。

除了非标准可变长度数组的问题(参见Heart的评论(,你的概念行不通。

缺陷是代码没有考虑必须跳过两个或多个小值才能找到解决方案的可能性。(另请注意,您显示的代码和表不匹配。代码从不执行行1 1 6 6 10(。

例如,考虑以下顺序:

{4, 1, 6, 3, 4}

唯一有效的分区是{4, 1, 4}{6, 3}。此分区需要跳过两个分区的两个小条目,这是不支持的。

运行如下所示:

| 4   1   6    3   4
--+-------------------
4 |(0)  1   6    3   4
1 | 1  (1)  7    4   5
6 | 7   7  (7)   4   5 
3 | 7   7   7   (7)  8
4 | 7   7   7    7  (8)

每列都有其问题。

  1. (使用 4( 跳过前 4 个,因此它放弃了{4, 1, 4}子集。所以它应该跳过1,但由于它添加了它,此列将不起作用。
  2. (带 1( 添加1,因此只能扩展到{4, 1, 4}。所以它应该跳过6,但它最后补充说......
  3. (6(添加6,因此它只能扩展到{4, 1, 4},然后添加1,这是可以的。但是它增加了3,因为它足够小(总和 4+1+3 == 8(。但是3它应该跳过它。
  4. (使用 3( 添加3+1+4<9,代码接受该集合,但不会导致有效集合。
  5. (带4(加4+1+3<9,同上。

如果要使用动态编程,则必须按照您发布的链接进行操作。为此,您需要一个包含sum(v) / 2 + 1元素的vector<bool>,在第一个元素上使用true初始化它,并在其余元素上false。您可以仅使用一个vector,因为您只对找出是否有任何解决方案感兴趣,而不是返回它。

您的复杂度将是 O(sum(v( * v.size(((,如果值很大,则可能太多。

如果值很大,则可以改用std::unordered_set<int>来编写与上述vector<bool>相同的代码,但很少。复杂性更难推理。最坏的情况是每个子集都有不同的总和(例如 {1, 2, 4,..., 2 N-2}, K>>>2 N-1}(。这将使成本 O(2N( 其中 N = v.size((。这比第一种算法要好,第一种算法的成本为O(K(,在这种情况下比O(2N(差得多。