我有一些像
这样的随机整数99 20 30 1 100 400 5 10
我必须从这些整数的任意组合中找到一个总和,它与给定的数字最接近(等于或多于但不少),如
183
这样做的最快和最准确的方法是什么?
答案 0 :(得分:3)
这是SUBSET-SUM问题的变体,也是像SUBSET-SUM一样的NP-Hard。
但如果涉及的数字很小,则存在伪多项式时间算法。退房:
http://en.wikipedia.org/wiki/Subset_sum_problem
确定更多细节。
以下问题:
给定一个整数数组,并且整数a,b,就在那里 一些子集的总和在于 区间[a,b]是NP-Hard。
这是因为我们可以通过选择a = b = 0来解决子集和。
现在这个问题很容易减少到你的问题所以你的问题也是NP-Hard。
现在您可以使用上面wiki链接中提到的多项式时间近似算法。
给定N个整数的数组,目标S和近似阈值c,
有一个多项式时间近似算法(涉及1 / c),它告诉区间[(1-c)S,S]中是否存在子集和。
你可以反复使用它(通过某种形式的二分搜索)来找到你需要的最佳近似值。注意你也可以在[S,(1 + c)S]的间隔使用它,而背包只会给你一个解决方案< = S.
当然可能有更好的算法,实际上我可以赌它。网上应该有大量的文献。您可以使用的一些搜索术语:子集和的近似算法,伪多项式时间算法,动态规划算法等。
答案 1 :(得分:3)
这似乎是Knapsack-like problem,其中整数的值将是每个项目的“权重”,每个项目的“利润”为1,并且您正在寻找最少数量的项目准确地计算背包的最大允许重量。
答案 2 :(得分:3)
如果你的数字很小,你可以使用简单的Dynamic Programming(DP)技术。不要让这个名字吓到你。这项技术是可以理解的。基本上你将更大的问题分解为子问题。
这里我们将问题定义为can[number]
。如果number
可以根据文件中的整数构建,则can[number]
为true
,否则为false
。很明显0
可以完全不使用任何数字,因此can[0]
是true
。现在,您尝试使用输入文件中的每个数字。我们试着看看总和j
是否可以实现。如果already achieved sum + current number we try == j
,那么j
显然是可以实现的。如果要跟踪哪些数字成为特定总和,请使用额外的prev
数组,该数组存储最后使用的数字以进行总和。请参阅以下代码以实现此想法:
int UPPER_BOUND = number1 + number2 + ... + numbern //The largest number you can construct
bool can[UPPER_BOUND + 1]; //can[number] is true if number can be constructed
can[0] = true; //0 is achievable always by not using any number
int prev[UPPER_BOUND + 1]; //prev[number] is the last number used to achieve sum "number"
for (int i = 0; i < N; i++) //Try to use every number(numbers[i]) from the input file
{
for (int j = UPPER_BOUND; j >= 1; j--) //Try to see if j is an achievable sum
{
if (can[j]) continue; //It is already an achieved sum, so go to the next j
if (j - numbers[i] >= 0 && can[j - numbers[i]]) //If an (already achievable sum) + (numbers[i]) == j, then j is obviously achievable
{
can[j] = true;
prev[j] = numbers[i]; //To achieve j we used numbers[i]
}
}
}
int CLOSEST_SUM = -1;
for (int i = SUM; i <= UPPER_BOUND; i++)
if (can[i])
{
//the closest number to SUM(larger than SUM) is i
CLOSEST_SUM = i;
break;
}
int currentSum = CLOSEST_SUM;
do
{
int usedNumber = prev[currentSum];
Console.WriteLine(usedNumber);
currentSum -= usedNumber;
} while (currentSum > 0);
答案 3 :(得分:0)
一种简单的暴力方法是读取文本,将其解析为数字,然后遍历所有组合,直到找到所需的总和。
更快的解决方案是对数字进行排序,然后...... 添加最大数字到你的总和,它太大了吗?如果是这样,把它拿下来尝试下一个最小的。 如果总和太小,请添加下一个最大数字并重复。 继续添加数字,不要使总和超过目标。当你击中目标时完成。
请注意,当您回溯时,您可能需要回溯多个级别。听起来像递归的好例子......
答案 4 :(得分:0)
如果数字很大,您可以将其转换为整数程序。使用Mathematica
解算器,它可能看起来像这样
nums = {99, 20, 30 , 1, 100, 400, 5, 10};
vars = a /@ Range@Length@nums;
Minimize[(vars.nums - 183)^2, vars, Integers]
答案 5 :(得分:0)
您可以对值列表进行排序,找到大于目标的第一个值,并开始专注于小于目标的值。找到最接近目标的总和而不经过,然后将其与大于目标的第一个值进行比较。如果最近总和与目标之间的差异小于大于目标的第一个值与目标之间的差异,那么你就得到了最接近的总和。
有点儿,但我认为逻辑悬而未决。