给定一个目标总和和一组整数,找到与该目标相加的最接近的数字子集
Posted
技术标签:
【中文标题】给定一个目标总和和一组整数,找到与该目标相加的最接近的数字子集【英文标题】:Given a target sum and a set of integers, find the closest subset of numbers that add to that target 【发布时间】:2013-11-03 12:30:27 【问题描述】:我有一组整数 M 和一个目标总和 k。我想找到 M 的子集,当它加在一起时最接近 k 而不会超过。
例如:
M = 1, 3, 5, 5, 14
k = 12
answer = 1, 5, 5
because 1 + 5 + 5 = 11 and there is no way to make 12.
我有一个额外的约束,即子集最多可以包含 4 个元素。
在我的应用程序中,|M| 的大小可以很大(大约有数千个元素)。如果无法在合理的时间内找到最佳答案,我会对至少给出“好”答案的解决方案感兴趣。
现在我正在通过生成 10,000 个随机子集并选择最接近的子集来解决这个问题,这比人们预期的要好,但速度很慢。我不确定这实际上离最优还有多远,但我对此的任何见解都会很有趣。
【问题讨论】:
再确认一下,您想要实际的子集,而不仅仅是总和? 单个整数值有多大?其中有没有负面影响? 整数都是正数。它们跨越大约 7 个数量级(即从 1 到 1M),但大多数是 [1...10000]。 是的,我正在寻找最大阶数为 4 的最接近的子集。 【参考方案1】:由于您可以选择的项目数量有限,因此您可以使用相当简单的算法来做到这一点。
该算法以“代”生成可能的总和。一代的每个元素都包含一个表示总和的数字,以及用于构建该总和的 M
中的 N 元组索引。
零代为空;代X+1
是通过遍历代X
,并将M
的元素添加到该代的每个值,并记录它们的总和以用于下一代X+1
。
在计算总和之前,检查它的 N 元组是否存在您要添加的数字的索引。如果它在那里,请跳过该数字。接下来,检查总和:如果它已经存在于 X+1
总和中,则忽略它;否则,记录新的总和以及新的 N 元组索引(将您从一代 X
添加到 N 元组的数字的索引)。
这是您的输入的工作方式:
0代:空
第一代:
1 - 0
3 - 1
5 - 2
14 - 4
第 2 代:
4 - 0, 1
6 - 0, 2
8 - 1, 2
10 - 2, 3
15 - 0, 4
17 - 1, 4
19 - 2, 4
第三代:
9 - 0, 1, 2
11 - 0, 2, 3
13 - 1, 2, 3
18 - 0, 1, 4
20 - 0, 2, 4
22 - 1, 2, 4
24 - 2, 3, 4
第 4 代:
14 - 0, 1, 2, 3
23 - 0, 1, 2, 4
25 - 0, 2, 3, 4
27 - 1, 2, 3, 4
您现在可以在四代中搜索最接近您的目标号码k
的号码。
【讨论】:
这是一种重复使用工作的聪明方法,因为它可以进行详尽的搜索。谢谢你的想法。 @JohnShedletsky 这种“聪明的方式”通常被称为dynamic programming。 这是最坏情况下的 O(n^4),对吧?如果没有重叠和,则第 4 代将有 n^4 个元素。 @Dukeling 是的,为了实现任何加速,算法需要重叠。如果没有重叠,算法将在 O(n^4) 中运行。 很好地提到了 X 一代!【参考方案2】:如果目标总和 k 不太大,请查看 http://en.wikipedia.org/wiki/Subset_sum_problem#Pseudo-polynomial_time_dynamic_programming_solution - 您可以使用它创建一个位图,告诉您可以使用您的子集生成哪些数字。然后只需在位图中选择最接近 k 的数字即可。
【讨论】:
近似算法在这里不是更有意义吗? 我所描述的具有一个易于计算的成本,最多为 Mk,并且 - 如果你能负担得起 - 给出正确的答案。他们是否可以证明这一点取决于OP。如果您想要一个近似值,请将所有数字四舍五入到某个选定 G 的某个倍数,然后除以 G。这通过有效地将 k 减少到 k/G 来降低成本。【参考方案3】:将问题分成4个部分:
恰好包含 1 个元素的总和
简单循环,找到不大于目标的最大值。
恰好包含 2 个元素的总和
使用双 for 循环找出不大于目标的最大总和。
恰好包含 3 个元素的总和(类似于 3SUM)
对元素进行排序
使用双 for 循环,对目标减去两个值进行二分搜索,寻找较小的值以找到不大于目标的最大总和。
恰好包含 4 个元素的总和
对元素进行排序(已经完成)
使用双 for 循环生成 2 个元素的所有总和。
现在,对于每个这样的总和,对目标的总和进行二分搜索,寻找较小的值,直到找到一个不包含构成该总和的任何一个值的值。
请参阅this,了解使用这种方法解决类似问题的代码(确切的总和)。
平均案例运行时间 (?) = O(n + n^2 + n^2 log n + n^2 log n) = O(n^2 log n)
.
确定最后一个问题的运行时间有些困难,在最坏的情况下可能与O(n^4 log n)
一样糟糕,因为您最终可能会在找到适合的问题之前查看其中的大部分,但这应该很少发生,并且在同一次运行中,一些应该花费更少的时间,因此整体运行时间可能会更短。
【讨论】:
以上是关于给定一个目标总和和一组整数,找到与该目标相加的最接近的数字子集的主要内容,如果未能解决你的问题,请参考以下文章
java 给定一个整数数组,返回两个数字的索引,使它们相加到特定目标。
java 给定一个整数数组,返回两个数字的索引,使它们相加到特定目标。
java 给定一个整数数组,返回两个数字的索引,使它们相加到特定目标。
java 给定一个整数数组,返回两个数字的索引,使它们相加到特定目标。