分割数字的算法

时间:2011-11-30 23:05:23

标签: performance algorithm

给定正整数X,如何将其分为N部分,每个部分在AB之间,其中A <= B也是正整数?也就是说,写

X = X_1 + X_2 + ... + X_N

其中A <= X_i <= BX_i的顺序无关紧要?

3 个答案:

答案 0 :(得分:7)

如果您想知道数量这样做,那么您可以使用生成函数。

基本上,您对integer partitions感兴趣。 X的整数分区是将X写为正整数之和的一种方法。设p(n)n的整数分区数。例如,如果n=5然后p(n)=7对应于分区:

5
4,1
3,2
3,1,1
2,2,1
2,1,1,1
1,1,1,1,1

p(n)的生成函数是

sum_{n >= 0} p(n) z^n = Prod_{i >= 1} ( 1 / (1 - z^i) )

这对您有什么作用? 通过展开右侧并取系数z^n,您可以恢复p(n)。不要担心产品是无限的,因为你只会用有限的多个术语来计算p(n)。事实上,如果这就是您想要的,那么只需截断产品并停在i=n

为什么这样做? 请记住

1 / (1 - z^i) = 1 + z^i + z^{2i} + z^{3i} + ...

因此z^n的系数是写入方式的数量

n = 1 * a_1 + 2 * a_2 + 3 * a_3 + ...

现在我想a_i in分区中显示的次数。

这是如何概括的? 很容易,事实证明。从上面的描述中,如果您只希望分区的某些部分位于给定的集合A中,那么不要将产品放在所有i >= 1上,而只需将产品放在i in A上。设p_A(n)n的整数分区数,其部分来自集合A。然后

sum_{n >= 0} p_A(n) z^n = Prod_{i in A} ( 1 / (1 - z^i) )

同样,在此扩展中取z^n系数可以解决您的问题。但我们可以进一步跟踪分区的部分数量。为此,请添加其他占位符q以跟踪我们正在使用的部分。设p_A(n,k)nk个部分的整数分区数,其中部分来自集合A。然后

sum_{n >= 0} sum_{k >= 0} p_A(n,k) q^k z^n = Prod_{i in A} ( 1 / (1 - q*z^i) )

因此,取q^k z^n系数会将n的整数分区数提供给k部分,其中部分来自集合A

如何编写代码? 生成函数方法实际上为您提供了生成问题所有解决方案的算法以及从中均匀采样的方法解决方案集。选择nk后,右侧的产品就是有限的。

答案 1 :(得分:1)

这是一个解决这个问题的python解决方案,这是非常不优化的,但我试图尽可能简单地说明解决这个问题的迭代方法。

此方法的结果通常是最大值和最小值的列表,其间可能有1或2个值。因此,在那里有一个小优化(使用abs),这将阻止迭代器不断尝试找到从最大值向下计数的最小值,反之亦然。

这样做的递归方式看起来更优雅,但这将完成工作,并希望能帮助您找到更好的解决方案。


SCRIPT:

# iterative approach in-case the number of partitians is particularly large
def splitter(value, partitians, min_range, max_range, part_values):
    # lower bound used to determine if the solution is within reach
    lower_bound = 0
    # upper bound used to determine if the solution is within reach
    upper_bound = 0
    # upper_range used as upper limit for the iterator
    upper_range = 0
    # lower range used as lower limit for the iterator
    lower_range = 0
    # interval will be + or -
    interval = 0

    while value > 0:
        partitians -= 1
        lower_bound = min_range*(partitians)
        upper_bound = max_range*(partitians)
        # if the value is more likely at the upper bound start from there
        if abs(lower_bound - value) < abs(upper_bound - value):
            upper_range = max_range
            lower_range = min_range-1
            interval = -1
        # if the value is more likely at the lower bound start from there
        else:
            upper_range = min_range
            lower_range = max_range+1
            interval = 1

        for i in range(upper_range, lower_range, interval):
            # make sure what we are doing won't break solution
            if lower_bound <= value-i and upper_bound >= value-i:
                part_values.append(i)
                value -= i
                break

    return part_values


def partitioner(value, partitians, min_range, max_range):
    if min_range*partitians <= value and max_range*partitians >= value:
        return splitter(value, partitians, min_range, max_range, [])
    else:
        print ("this is impossible to solve")

def main():
    print(partitioner(9800, 1000, 2, 100))

此脚本背后的基本思想是价值需要介于min*partsmax*parts之间,对于解决方案的每个步骤,如果我们始终实现此目标,我们最终会在{对于min < value < max {1}},所以如果我们不断地从价值中移除,并将其保持在parts == 1范围内,我们将始终找到可能的结果。

对于此代码的示例,它基本上总是会占用min < value < maxmax,具体取决于min更接近哪个边界,直到某些非value或{ {1}}值将作为余数留下。

答案 2 :(得分:0)

您可以做的一个简单的实现是X_i的平均值必须介于AB之间,因此我们可以简单地将X除以{{1}然后做一些小的调整以均匀地分配余数以获得有效的分区。

这是一种方法:

N

如果X_i = ceil (X / N) if i <= X mod N, floor (X / N) otherwise. A <= floor (X / N),则会提供有效的解决方案。否则,没有解决方案。见下面的证明。


ceil (X / N) <= B

<强>证明:

使用除法算法将sum(X_i) == X 写为X = q*N + r

  • 如果0 <= r < N,则为r == 0,因此算法会设置所有ceil (X / N) == floor (X / N) == q。他们的总和是X_i = q

  • 如果q*N == X,则r > 0floor (X / N) == q。该算法为ceil (X / N) == q+1设置X_i = q+1(即1 <= i <= r个副本),为剩余的r个设置X_i = q。因此,总和为N - r


如果(q+1)*r + (N-r)*q == q*r + r + N*q - r*q == q*N + r == Xfloor (X / N) < A,则没有解决方案。

<强>证明:

  • 如果ceil (X / N) > Bfloor (X / N) < A以及floor (X / N) * N < A * N,则表示floor(X / N) * N <= X,因此即使只使用最小的部分,总和也是大于X < A*N

  • 同样,如果Xceil (X / N) > B以及ceil (X / N) * N > B * N,则表示ceil(X / N) * N >= X,即使只使用最大可能的部分,将小于X > B*N