为啥我的子集总和方法不正确?
Posted
技术标签:
【中文标题】为啥我的子集总和方法不正确?【英文标题】:Why is my approach to subset sum incorrect?为什么我的子集总和方法不正确? 【发布时间】:2020-05-27 10:25:30 【问题描述】:我是动态编程的新手,并提出了我(显然不正确)解决子集和问题的方法。我想知道为什么我的方法不正确。特别是我很好奇基本想法是否有效,或者我是否应该坚持使用子集和see yt的正常方法。
问题: 给定一个数字数组,在数组中找到两个总和相同的子集。与正常的子集和问题相比,这个问题略有改变。
示例: [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 个时终止。
实施:
Play with the code
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]
。根据site,它应该返回true,但它返回false。
【问题讨论】:
C++ 没有可变长度数组(一些编译器允许它们作为扩展)。如果您传递一个适当大的向量,代码long long mem[v.size() + 1][v.size()];
也会炸毁堆栈。我在 Godbolt 上尝试了一个 1100 元素向量,但它出错了。
好点。我认为在更好的版本中,我会使用矢量。那会干净得多。尽管如此,godbold 中的示例似乎是不正确的。所以问题是:这种跟踪总和的想法是否有效?
【参考方案1】:
除了非标准变长数组的问题(见Blastfurnace 的评论),你的概念是行不通的。
缺陷在于您的代码没有考虑必须跳过两个或更多小值才能找到解决方案的可能性。 (另请注意,您显示的代码和表格不匹配。代码从不执行行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)
每一列都有它的问题。
-
(With 4) 跳过前 4 个,因此它放弃了
4, 1, 4
子集。所以它应该跳过1
,但是因为它添加了它,所以这个列将不起作用。
(With 1) 添加1
,因此它只能扩展到4, 1, 4
。所以它应该跳过6
,但它最后会添加...
(With 6) 加6
所以只能扩展为4, 1, 4
,然后加1
,就可以了。但随后它添加了3
,因为它足够小(总和 4+1+3 == 8)。但是3
应该跳过它。
(With 3) 添加3+1+4
(带4)加4+1+3
如果你想使用动态编程,你必须遵循link you posted。为此,您需要一个带有sum(v) / 2 + 1
元素的vector<bool>
,在第一个元素上使用true
对其进行初始化,在其余元素上使用false
对其进行初始化。您可以只使用一个vector
来执行此操作,因为您只想了解如果有任何解决方案,而不是返回它。
您的复杂度将是 O(sum(v) * v.size()),如果值很大,这可能会太大。
如果值很大,您可以改用std::unordered_set<int>
来编写与上述vector<bool>
相同的代码,但要稀疏。复杂性更难推理。最坏的情况是每个子集都有不同的总和(例如 1, 2, 4, 8,..., 2N-2, K >>> 2N- 1)。这将使成本为 O(2N),其中 N = v.size()。这比第一个算法要好,后者的成本为 O(K),在这种情况下比 O(2N) 要差得多。
【讨论】:
以上是关于为啥我的子集总和方法不正确?的主要内容,如果未能解决你的问题,请参考以下文章
为啥寄存器 ax 中的内存整数总和正确,但寄存器 eax 不正确?