独特子集生成的动态规划实现

时间:2016-10-21 21:48:15

标签: algorithm optimization dynamic-programming

我遇到了这个问题,我们需要找到可以形成n子集的总方式(其中n是用户的输入)。子集条件是:子集中的数字应该是不同的,子集中的数字应该是递减的顺序。

示例:给定n = 7,输出为4,因为可能的组合是(6,1)(5,2)(4,3)(4,2,1)。请注意,虽然(4,1,1,1)也加起来为7,但它有重复的数字。因此它不是有效的子集。

我使用具有指数复杂性的回溯方法解决了这个问题。但是,我想弄清楚如何使用Dynamic Prog解决这个问题?我能想出的最接近的是系列:对于n = 0,1,2,3,4,5,6,7,8,9,10,其输出为0,0,0,1,1,分别为2,3,4,5,7,9。但是,我无法想出一个可以用来计算f(n)的通用公式。在浏览互联网时,我在https://oeis.org/search?q=0%2C0%2C0%2C1%2C1%2C2%2C3%2C4%2C5%2C7%2C9%2C11%2C14&sort=&language=&go=Search中遇到了这种整数序列。但我无法理解他们在这里提供的公式。任何帮助,将不胜感激。任何其他提高指数复杂性的见解也值得赞赏。

1 个答案:

答案 0 :(得分:0)

您所谓的“唯一子集生成”更为人所知的是integer partitions with distinct parts。 Mathworld在the Q function上有一个条目,它计算具有不同部分的分区数。

,您的函数不计算普通分区(即n -> (n)),因此您要查找的内容实际为Q(n) - 1,生成sequence that you linked in your question } - 至少2个不同部分的分区数。 This answer关于数学的类似问题包含一个有效的Java算法,用于计算最多n = 200的序列,可以很容易地适应更大的值。

以下是引用算法的组合解释:

让我们从{1, 2, 3}的所有子集开始,按其总和进行分组:

 index (sum)    partitions        count
     0           ()                 1
     1           (1)                1 
     2           (2)                1
     3           (1+2), (3)         2
     4           (1+3)              1
     5           (2+3)              1
     6           (1+2+3)            1

假设我们要构建一个包含{1, 2, 3, 4}所有子集的新表。请注意,{1, 2, 3}的每个子集也是{1, 2, 3, 4}的子集,因此上面的每个子集都会出现在我们的新表中。实际上,我们可以将新表划分为两个大小相同的类别:的子集包含4,以及那些包含4的子集。我们可以做的是从上面的表开始,复制它,然后用{1, 2, 3, 4}“扩展”它。这是 index (sum) partitions count 0 () 1 1 (1) 1 2 (2) 1 3 (1+2), (3) 2 4 (1+3), [4] 2 5 (2+3), [1+4] 2 6 (1+2+3),[2+4] 2 7 [1+2+4],[3+4] 2 8 [1+3+4] 1 9 [2+3+4] 1 10 [1+2+3+4] 1 的表格:

4

包含4的所有子集都用方括号括起来,它们是通过将{1,2,..,5}添加到括号括起来的旧子集中而形成的。我们可以重复此过程并为{1,2,..,6}{1, 2, 3}等构建表格。

但我们实际上并不需要存储实际的子集/分区,我们只需要每个索引/总和的计数。例如,如果只有{1, 2, 3, 4}的表只有计数,我们可以通过从旧表中取每个(index, count)对并添加{{1}来构建count的计数表。 } {到index + 4的当前计数。我们的想法是,如果有{1, 2, 3}的两个子集总和为3,那么将4添加到这两个子集中的每个子集将为{{1}创建两个新的子集}。

考虑到这一点,这是这个过程的Python实现:

7

我们从def c(n): counts = [1] for k in range(1, n + 1): new_counts = counts[:] + [0]*k for index, count in enumerate(counts): new_counts[index + k] += count counts = new_counts return counts 的表开始,它只有一个子集 - 空集 - 总和为零。然后我们为{}构建表格,然后为{1},...构建表格,一直到{1, 2}。完成后,我们会计算{1,2,..,n}的每个分区,因为n的分区不能包含大于n的整数。

现在,我们可以对代码进行两项主要优化:

  1. 将表限制为仅包含最多n的条目,因为这是我们感兴趣的所有内容。如果n超过index + k,那么我们就会忽略它。我们甚至可以预先为最终表预先分配空间,而不是在每次迭代时增加更大的表。

  2. 不是在每次迭代时从头开始构建一个新表,如果我们小心地向后迭代旧表,我们实际上可以将就地更新它而不会弄乱任何新的值。

  3. 通过这些优化,您可以有效地使用之前引用的the Mathematics answer中的相同算法,这是通过生成函数来实现的。它在n时间内运行,仅占用O(n^2)个空间。这是它在Python中的样子:

    O(n)