查找最大分区的另一种方法

数字总是很有趣!

您正在组织一次黑客马拉松,并决定为获奖者提供免费的云存储作为奖励。 对于奖金,您拥有1024 GB的云空间。 您可能会给这些GB数据,条件是黑客马拉松中的较高位置将获得更大的空间。 由于您希望使尽可能多的参与者感到高兴,因此您想找到要为其奖励的最大席位。 这意味着,如果您只有8 GB的可用空间,那么您将总共拥有3个职位-获胜者获得5 GB,亚军获得2 GB,排在第三位的人获得1 GB(可能会有其他变化-4,3和1 GB,但8 GB的位置数仍为3)。

那么您如何解决呢? 请注意(如以上示例所示),对于给定数量的职位(我们称其为p ),可能存在多种分布。 确实,这归结为以不同的较小数字的总和表示一个数字,使得这些数字越多越好。 对于8个演出,我们可以选择8 = 7 + 18 = 5 + 3的形式 ,但这并不是最佳选择,因为8可以表示为多个数字的总和,例如在8 = 5 + 2 +1 。 一个可以为解决此问题增加便利的数学概念是分区的概念-Wikipedia引用正整数n (也称为整数)的分区 分区,是将n表示为正整数之和的一种方式。 因此,在我们的情况下,我们只想计算具有尽可能多的数字的512分区。 为了进行对话,我们将其称为最大分区

在计算机科学中,此问题属于一类问题,其解决方案使用贪婪算法-这些过程在解决方案的每个阶段都做出局部最优选择,希望找到全局最优值。

解决我们的示例的“贪婪”方法如下:以1作为第一个被乘数似乎不自然吗? 剩下的就是将7表示为最大分区,并向其添加1。 但是现在将7表示为最大分区有一个约束-我们不能使用1。因此,我们使用2并继续将8-(1 + 2)= 5表示为最大分区。 同样,为此,我们不能使用1和2。我们也不能使用3或4,因为那样的话,我们最终将分别使用2和1。 因此,我们仅将5表示为自身,我们已经完成了-现在,我们的最大分区为8 = 1 + 2 + 5 。 可以很容易地看出,当我们最初想要弹出的数字(此处为3 )至少是剩余数字(此处为5 )的一半时,就会出现这种最终条件。 我把那部分留给你弄清楚。

因此,更正式地讲这个策略-考虑我们最初有两个数字n = 8l = 1 。 如果n≤2l 我们简单地将n表示为自身,否则我们弹出l ,然后解决将n-1表示为最大分区的子问题,以使分区中的每个数字至少为l + 1。 该子问题的l值比原始问题大1。 因此,对于我们的代表8作为最大分区的示例(N,L),我们首先弹出1,然后求解子(NL,L + 1),(7,2)。 对于这个子问题,我们弹出2,然后求解子(7 -2,2 + 1),(5,3)。 现在,由于5≤2x3 ,我们只弹出5就完成了。 现在,我们将弹出的数字相加即可得出8 = 1 + 2 + 5

由于我们现在已经更正式地阐明了该策略,因此很容易提出一个工作程序来解决我们的问题。 这是Python 3中一个简单的实现:

def max_partition(n, l=1):
partition = []
while n > 2*l:
partition.append(l)
n = n - l
l += 1
partition.append(n)
return partition
print(max_partition(int(input())))

但是,等等-我有另一种方法。 也许更好。 我注意到可以表示为前n个自然数之和的数字是特殊的。 当炫耀自己的身份时,他们不是已经处于最大分区形式了吗? 例如,当6书写为6 = 1 + 2 + 3时,不是6已经处于最大分区形式吗? 当10写入10 = 1 + 2 + 3 + 4时,不是10已经处于最大分区形式吗? 为了进行对话,我们将此类数字称为“高级数字”。 这种见解构成了我算法的基础。

我们的操作方法如下:如果要查找的最大分区数n已经是一个高级数,我们可以简单地用n = 1 + 2 + 3 +…+ k的形式表示它。 如果它不是高阶数,我们仍然会找到一个k ,它正好足以使总和s = 1 + 2 + 3 +…+ k大于n (足够大,我的意思是k可以使1+ 2 + 3 + …+ k大于n ,但对于1 + 2 + 3 +…+ k-1不能做相同的事情。 由于k刚好足以使s大于n ,因此sn将小于k 。 所以SN将是1,2,3,...,K-1之间的数字。 如果我们从前k个自然数的总和中“抽取” sn怎么办? 那将给我们s-(sn) ,不过就是n

顺便说一句,上一段的最后一个字符不是阶乘 ial。 让我们形象化到目前为止所学到的想法。 通常,使用Ferrers图来可视化分区,但是出于我们的目的,我发现自定义可视化更加方便:在下面的树中,顶部节点是我们希望评估的最大分区数。 叶子是max-partition表示中的数字,当然,将这些数字相加即可得出顶部节点上的数字。 对于资深人士来说,一切都很好:

对于不高级的数字,我们剪切适当的分支,以便不添加通过该分支连接到顶部节点的叶子上的数字。 考虑9的情况:

下面是8的树。请注意,我们只是找到数字k。 对于8和9,它是4。由于前4个自然数的总和是10,我们首先为10绘制树,然后在顶层节点中将10替换为我们想要的数字-这是8。然后,将连接的分支剪掉数字10 – 8 = 2的顶部节点。对于9,该数字为10-9 = 1。

那么最大分区就是剩余叶子的总和。 希望您现在了解我的算法。

找出“足够大的k ”的方法还有待发现。 但这是一个非常简单的计算。 前k个自然数的总和为n = k *(k + 1)/ 2 。 在将该方程解为正数k之后 ,我们得到k =(√(1 + 8 * n)– 1)/ 2 。 由于如果n不是一个高级数字, k将是分数,因此我们对其进行了上限。 这使k足够大。

我认为到目前为止,我已经清楚地阐明了该算法。 因此,我们可以对解决方案进行编程。 这是Python 3中另一个简单的实现:

import math
def optimal_summands(n):
k = ((1 + 8*n)**0.5 - 1) / 2
k = math.ceil(k)
summands = list(range(1, k+1))
the_sum = int(k * (k+1) / 2)
if the_sum - n > 0: # If n is not senior.
del summands[the_sum-n-1]
return summands
print(optimum_summands(int(input())))

如果我们对这两种算法进行一些分析,就会发现它们都在线性时间内运行O(n) 。 然而,隐藏在O(n)的无形常数也许是optimal_summands()max_partition少得多()。

我在Python中做了一些简单的检查,看哪种方法更快,而后一种方法的执行速度是前一种方法的三倍 。 我使用Python的timeit模块对这两种算法进行计时,这是我对Python解释器进行的一项检查的实例:

>>> from timeit import timeit
>>>
>>> timeit(setup='from different_summands import optimal_summands', stmt='optimal_summands(10000)', number=100000)
0.8999944160023006
>>>
>>> timeit(setup='from greedy_different_summands import max_partition', stmt='max_partition(10000)', number=100000)
3.161972836998757
>>>

我经常观察到,了解一些数学事实可以使人们开发出一种更好的算法,或者至少可以更快,更直观地开发一种算法。 数学见解通常可以显着改善程序的运行时间。 数学和计算机科学-尤其是算法研究是很好的朋友!

图片来源:xkcd

如果您知道其他一些因素会使后者的程序运行更快,请在评论中告知我。 最后,也许贪婪并不总是很好,但数学却可以。 😀

顺便说一下,您可以将这1024 GB拆分为:

1024 = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45

(请注意缺少的数字吗?提示:这是前45个自然数总和-1024。)

当然,您的黑客马拉松必须有超过45位参与者! 🙂

PS:我应该为此写ArXiv论文吗?

From: https://hackernoon.com/another-way-to-find-max-partitions-36005590a2a5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值