给定一个目标总和和一组整数,找到与该目标相加的最接近的数字子集

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 给定一个整数数组,返回两个数字的索引,使它们相加到特定目标。

java 给定一个整数数组,返回两个数字的索引,使它们相加到特定目标。

来自 LeetCode 给定一个整数数组,返回两个数字的索引,使得它们相加到一个特定的目标