带范围数的推荐组合算法

Posted

技术标签:

【中文标题】带范围数的推荐组合算法【英文标题】:Recomended Combination Algorithms for Numbers with Ranges 【发布时间】:2015-11-10 20:04:36 【问题描述】:

我目前正在尝试编写 C# 代码来查找多个整数数组,当它们相加时等于指定的总数。我想找到这些组合,同时给数组中的每个整数一个范围。

例如,如果我们的总数是 10,并且我们有一个大小为 3 的 int 数组,其中第一个数字可以介于 1 和 4 之间,第二个数字可以介于 1 和 4 之间,第三个数字可以介于 3 和 6 之间,那么一些可能的组合是 [1 , 3, 6], [2, 2, 6] 和 [4, 2, 4]。

什么样的算法可以帮助解决这样的问题,并且可以在它们最有效的时间内运行?另外,在将此问题转换为 C# 代码时,我还应该记住哪些其他事项?

【问题讨论】:

我会使用递归来做到这一点,但很可能这不是最佳解决方案。 here 描述的算法应该可以完美地用于基本组合生成器。对于生成输入和过滤输出, Enumerable.Range 和 Linq .Where 将是最简单的选项 如果我给你一个解释的解决方案,你可以吗?或者你不想让任何人帮助你? 无论如何都可以解释一下。 【参考方案1】:

我会使用递归来做到这一点。您可以简单地遍历所有可能的值并查看它们是否给出所需的总和。

输入

假设我们有以下输入模式:

N S
min1 min2 min3 ... minN
max1 max2 max3 ... maxN

你的例子

如果我们的总数是 10 并且我们有一个大小为 3 的 int 数组,其中第一个 number 可以介于 1 和 4 之间,第二个 2 和 4,第三个 3 和 6

它将是:

3 10
1 2 3
4 4 6

解决方案

我们已经读取了我们的输入值。现在,我们只是尝试将每个可能的数字用于我们的解决方案。

我们将有一个List 来存储当前路径:

static List<int> current = new List<int>();

递归函数非常简单:

private static void Do(int index, int currentSum)

    if (index == length) // Termination
    
        if (currentSum == sum) // If result is a required sum - just output it
            Output();
        return;
    

    // try all possible solutions for current index
    for (int i = minValues[index]; i <= maxValues[index]; i++) 
    
        current.Add(i);
        Do(index + 1, currentSum + i); // pass new index and new sum
        current.RemoveAt(current.Count() - 1);
    

对于非负值,我们也可以包含这样的条件。这是递归改进,它将切断大量不正确的迭代。如果我们已经有一个大于sumcurrentSum,那么在这个递归分支中继续下去是没有用的:

if (currentSum > sum) return;

实际上,该算法是一种简单的“找到给出总和 S 的组合”的问题解决方案,但有一个区别:minValue[index]maxValue[index] 内的内循环索引。

演示

这是我的解决方案的工作IDEOne demo。

【讨论】:

【参考方案2】:

你不能比嵌套 for 循环/递归做得更好。虽然如果你熟悉 3SUM 问题,你就会知道一个小技巧来降低这种算法的时间复杂度!如果您有n 范围,那么在您做出第一个n-1 选择后,您就知道必须从第n 个范围中选择哪个数字了!

我将使用一个示例来说明我的建议。

如果我们的总数是 10,并且我们有一个大小为 3 的 int 数组,其中第一个数字可以介于 1 和 4 之间,第二个数字可以介于 2 和 4 之间,第三个数字可以介于 5 和 6 之间

首先让我们处理数据以便更好地处理。我个人喜欢使用从 0 开始而不是任意数字的范围的想法!所以我们从上界减去下界:

(1 to 4) -> (0 to 3)
(2 to 4) -> (0 to 2)
(5 to 6) -> (0 to 1)

当然,现在我们需要调整目标总和以反映新的范围。所以我们也从目标总和中减去原来的下界!

TargetSum = 10-1-2-5 = 2

现在我们可以只用上限来表示我们的范围,因为它们共享一个下限!所以范围数组看起来像:

RangeArray = [3,2,1]

让我们对此进行排序(稍后会更清楚为什么)。所以我们有:

RangeArray = [1,2,3]

太棒了!现在进入算法的重点……求和!现在我将使用for 循环,因为它更容易用于示例目的。您将不得不使用递归。 Yeldar 的代码应该为您提供一个良好的起点。

result = []
for i from 0 to RangeArray[0]:
    SumList = [i]
    newSum = TargetSum - i
    for j from 0 to RangeArray[1]:
        if (newSum-j)>=0 and (newSum-j)<=RangeArray[2] then
            finalList = SumList + [j, newSum-j]
            result.append(finalList)

注意内部循环。这就是 3SUM 算法的启发。我们利用了这样一个事实,即我们知道我们必须从第三个范围中选择什么值(因为它是由我们的前 2 个选择定义的)。

从这里你当然必须通过将原始下限添加到来自相应范围的值来将结果重新映射回原始范围。

请注意,我们现在明白为什么对RangeList 进行排序可能是个好主意。最后一个范围被吸收到第二个范围的循环中。我们希望最大的范围是不循环的。

我希望这有助于您入门!如果您在将我的伪代码翻译成 c# 时需要任何帮助,请询问 :)

【讨论】:

第 7 行(finalList = SumList + [j, newSum-j]),我不明白 [j, newSum-j] 是什么。你能解释一下吗? SumList + [j, newSum-j] 的意思是“附加了 j 和 newSum-j 的 SumList”。 j 是我们选择的中间范围的元素。由于我们已经选择了 2 个号码,并且只有 3 个范围可供选择,所以我们已经做出了最终的选择!我们必须选择newSum-j!

以上是关于带范围数的推荐组合算法的主要内容,如果未能解决你的问题,请参考以下文章

算法789. 数的范围——二分

8086汇编输出指定阶数的乘法口诀表(带提示信息,范围:1~181,其中1~9阶表左对齐输出)

8086汇编输出指定阶数的乘法口诀表(带提示信息,范围:1~181,其中1~9阶表左对齐输出)

8086汇编输出指定阶数的乘法口诀表(带提示信息,范围:1~181,其中1~9阶表左对齐输出)

ACwing_789. 数的范围

用C/C++码经典算法