在准备面试时发现了这个问题。
假设一些毛虫从底部开始并跳到下一片叶子。他们 在跳到下一个之前吃叶子。我们得到一个代表跳跃步骤的数组 由毛毛虫制成。如果阵列是[2,4,7],则意味着卡特彼勒[0]会吃叶2,4,6 .. 毛虫[1]会吃叶4,8,12 ..而毛虫[2]会吃7,14,21 ... 0代表地面。 计算未吃叶子的数量。
让我们假设如果吃掉当前的叶子,毛虫会跳到下一个目的地。这意味着,如果毛虫[7]发现叶子28被吃掉了,它就会继续吃叶子35。
设c为毛毛虫的数量,n为叶数。
显而易见的蛮力解决方案是针对每只毛毛虫在大小为n的布尔阵列上进行迭代,如果吃掉则将其标记为真,否则将其标记为假。它需要O(n * c)时间。我们可以做得更好吗?
答案 0 :(得分:12)
毛毛虫吃掉其“跳跃步骤”j
的所有多个部分,因此如果它是单独的,每条毛毛虫都会吃掉floor(n/j)
个叶子。
现在你必须弄清楚你已经多次计算了哪些叶子。例如,如果您计算第一条毛毛虫可以分割2的所有叶子,那么您不必计算第二条毛虫的任何叶子,第4条毛虫每4分跳一次。
对于两个项目,这两个项目的数字是两个项目的least common multiple的倍数,其中有floor(n/lcm(j,j'))
个。
请注意,对于三个术语,如果执行此计算,则可能会删除一些项目两次:让我们在您的示例中使用28。它会被毛毛虫用跳跃步骤7吃掉,但是要计算其他两个(因为28%4 == 28%2 == 0),因此你需要添加多次删除的倍数:floor(n/lcm(j,j',j"))
你可以在这里看到一个模式,它是the inclusion-exclusion principle。通用公式如下:
让Aj成为毛毛虫吃的叶子,跳跃步骤j(如果它是单独的)。然后对于J一组几个capterpillar跳跃集,A J 是所有这些毛虫吃掉的叶子。
让我们将一个集合的最小公倍数定义为集合中所有元素的最小公倍数,因此我们可以编写lcm(J)
。
包含 - 排除公式中的[n]是一组被考虑的毛虫跳跃,因此在您的情况下[2,4,7]
,我们迭代它的所有子集。 |J|
是子集的大小,| A J |是J中每只毛虫可以吃的叶子数量的大小,所以我们得到| A J | = floor(n/lcm(J))
。
您现在有2个 c 项*的总和,因为这是c
毛虫的子集数。请注意,您可以通过保存最不常见的倍数来节省一些时间,而不是从头开始重新计算它们。
我将实际代码写成“练习”,正如一些人所说的那样:它基本上是迭代子集并计算最不常见的倍数,然后将它们全部放在上面的总和中。
这可以获得吃掉叶子的总数。从这里获取未经治疗的人是微不足道的。
如果我们在一个小例子(能够检查)上做,地面为0,叶子为1..24
,卡特彼勒跳跃步骤为[2,3,4]
。
唯一幸存的叶子将是{1,5,7,11,13,17,19,23}:删除所有偶数和所有可分数为3的数字。也就是说,我们希望答案为8。 / p>
j=2
单独吃24/2 = 12片叶子j=3
单独吃24/3 = 8片叶子j=4
单独吃24/4 = 6片叶子j=2
和j=3
都想吃24/6 = 4片叶子:{6,12,18,24} j=3
和j=4
都喜欢吃24/12 = 2片叶子:{12,24} j=4
和j=2
都想吃24/4 = 6片叶子:所有被4
吃掉的人都被2
所以24 - 16 =剩下8片叶子。
*当然这是最糟糕的情况。希望您将迭代增加大小的子集,并且一旦子集J的最小公倍数大于n
,您可以忽略该J的所有超集。特别是,如果所有大小的子集k
比n大1cm,你可以停止迭代。
答案 1 :(得分:3)
这是关于你提到的O(n * c)算法。如果仔细观察,那就是O(n logc)。
一只毛毛虫吃了它的所有多个跳跃步骤' j,因此,如果它是单独的,每只毛毛虫都会吃掉地板(n / j)。
这种复杂性受到以下因素的限制: n + n / 2 + n / 3 + ... + n / c <= n log(c)
它不会有所作为,因为c很小但只是指出:)
点击此链接,了解Cimbali Inclusion-exclusion
对figure out Uneaten Leaves algorithm bug的实施情况编辑:以下是谐波系列以log(c)为界的证明。我们将不等式用于集成的下限。
答案 2 :(得分:0)
这只是对@ cimbali建议的方法的优化。从包含卡特彼勒步幅的阵列中。您可以从该数组中删除步长值的倍数,以减少找到的组合数。
例如24片叶子,[2,3,4]毛虫跳跃步骤。
第一步: 浏览步幅数组并删除2的倍数。因为4是2的倍数。从数组中删除4
第二步:大小为1的子集。 卡特彼勒j = 2,单独吃24/2 = 12叶 卡特彼勒j = 3,单独吃24/3 = 8叶
第三步:大小为2的子集。 卡特彼勒j = 2和j = 3都喜欢吃24/6 = 4片叶子:{6,12,18,24}
所以24 - 16(12 + 8-4)=剩下8片叶子。
答案 3 :(得分:0)
复杂度不能为O(N)甚至O(N / K)。我的算法是O(2 ^ K),它本身很大,但是仍然可以接受。即使我通过N = Long.MAX_VALUE,我也会立即得到结果。尽管如果K更大,则当所有跳转值的LCM超过Long.MAX_VALUE时,代码可能会花费一些时间或代码可能会中断。
为了说明这一点,让我们以A = {4,5,6}和N = 20为例。
我们可以算出未食用的叶子是{1、2、3、7、9、11、13、14、17、19} = 10
我们如何不计入此结果? N-(N / 4)-(N / 5)-(N / 6)+(N / 20)+(N / 12)+(N / 30)-N / 60 = 20-5-4-3 + 1 + 1 + 0-0 = 20-12 + 2 = 10