假设您有一组值( 1
, 1
, 1
, 12
, 12
, 16
),您将如何生成所有可能的组合(不重复),其总和在预定义范围 [min,max]
内 . 例如,以下是 13
和 17
之间范围的所有组合(所有深度):
1
12
1
1
12
1
1
1
12
16
1
16
这假设具有相同值的每个项目都是无法区分的,因此在最终输出中没有三个结果 1
12
. 蛮力是可能的,但是在物品数量很大的情况下,所有深度的组合数量都是天文数字 . 在上面的示例中,在所有深度处存在(3 1)(2 1)(11)= 24种组合 . 因此,总组合是任何给定值1的项目数的乘积 . 当然,我们可以逻辑地丢弃其部分和大于最大值的大量组合(例如,集合 16
12
已经大于最大值为 17
,因此请跳过其中包含 16
和 12
的任何组合 .
我原本以为我可以将输入数组转换成两个数组并将它们增加为类似里程表 . 但是我完全陷入了这种早期打破的递归算法 . 有什么建议?
{
int uniqueValues = 3;
int[] maxCounts = new int[uniqueValues];
int[] values = new int[uniqueValues];
// easy code to bin the data, just hardcoding for example
maxCounts[0] = 3;
values[0] = 1;
maxCounts[1] = 2;
values[1] = 12;
maxCounts[2] = 1;
values[2] = 16;
GenerateCombinationsHelper(new List<int[]>(), 13, 17, 0, 0, maxCounts, new int[3], values);
}
private void GenerateCombinationsHelper(List<int[]> results, int min, int max, int currentValue, int index, int[] maxValues, int[] currentCombo, int[] values)
{
if (index >= maxValues.Length)
{
return;
}
while (currentCombo[index] < maxValues[index])
{
currentValue += values[index];
if (currentValue> max)
{
return;
}
currentCombo[index]++;
if (currentValue< min)
{
GenerateCombinationsHelper(results, min, max, currentValue, index + 1, maxValues, currentCombo, values);
}
else
{
results.Add((int[])currentCombo.Clone());
}
}
}
Edit
整数值仅用于演示 . 它可以是具有某种数值的任何对象( int
, double
, float
等...)
通常只会有少数独特的值(约10个左右),但总共可以有数千个 .
5 回答
将主要呼叫切换到:
然后添加以下代码:
它返回5个有效答案 .
这类问题的一般趋势是,相对较少的值会出现,但每个值都会显示很多次 . 因此,您首先要创建一个数据结构,该数据结构可以有效地描述将添加到所需值的组合,然后才能找出所有这样做的组合 . (如果您知道术语“动态编程”,那正是我所描述的方法 . )
C#术语中明显的数据结构是Hashtable,其键是组合加起来的总数,其值是列出最后元素位置的数组,这些元素可以组合使用,可以累加到特定总数 .
你如何 Build 这种数据结构?
首先,您从一个Hashtable开始,该Hashtable包含总数0作为键,空数组作为值 . 然后,对于数组的每个元素,您可以创建一个可以从之前的总计中获得的新总计列表,并将元素的位置附加到它们的每个值(如果需要,可以插入一个新的值) . 当您浏览完所有元素后,您就拥有了自己的数据结构 .
现在,您可以仅针对所需范围内的总计搜索该数据结构 . 对于每个这样的总数,您可以编写一个递归程序,它将遍历您的数据结构以生成组合 . 这个步骤确实可以有一个组合爆炸,但好的是产生的 EVERY 组合实际上是你最后答案的组合 . 因此,如果这个阶段需要很长时间,那是因为你有很多最终答案!
试试这个算法
这与恰好是NP-complete的subset sum problem非常相似 .
维基百科说以下关于NP完全问题:
如果确实有一种方法可以解决这个问题,除了通过powerset强制执行并找到总和达到给定范围内的值的所有子集,那么我会非常有兴趣听到它 .
另一个实现的想法:
从数字列表中创建堆栈列表,每个堆栈代表一个出现在列表中的数字,并且这个数字被推入堆栈的次数与数字列表中出现的数量相同 . 更重要的是,此列表已排序 .
我们的想法是,您遍历堆栈列表,在每个堆栈中,如果它没有超过最大值,则一次弹出一个数字并调用该函数,并执行跳过当前堆栈的额外调用 .
该算法减少了许多冗余计算,例如在添加此值超过最大值时尝试添加具有相同值的不同元素 .
我能够用这个算法解决相当大的问题(50个数字和更多),这取决于最小值和最大值,显然当间隔非常大时,组合的数量可能很大 .
这是代码:
这是一个测试程序: