九章算法官网-原文网址
http://www.jiuzhang.com/problem/24/
题目
初阶:给一个区间[a,b],对于区间中每个数,如果是3的倍数,输出Fizz,如果是5的倍数,输出Buzz,如何能同时是3和5的倍数,输出FizzBuzz。
进阶:假如有很多个除数和对应的单词被放在一个Hash表中(上述例子为{3: “Fizz”, 5:”Buzz”})。请问有什么方法可以加快运算效率?
解答
初阶:for区间中的每个数,先判断能否被15整除,然后判断被3整除,然后判断被5整除。注意跳过0。
进阶:时间最优的方法-开一个区间那么大的数组,数组的每个元素是一个字符串,对于每个除数,枚举他在区间中的所有倍数,然后将单词加入数组中对应位置。时间复杂度O(n),n为答案的个数。空间复杂度O(b-a)
空间最优的方法-上述方法需要开辟一个大数组,在区间很大,而除数不多的情况下并不划算。对于每个除数,计算出每个除数的第一个大于等于左区间的倍数,然后放入一个堆。每次从堆中取出一个数,讲结果记录,并讲该结果加上对应除数,放回堆。时间复杂度O(nlogm),m为除数个数。空间复杂度O(m)。
面试官角度
不要小看初阶问题。初阶问题可以难倒99.5%的面试者。这个问题是著名的面试问题之一。他的考点主要在于编程实现和细节处理。如需要考虑区间a,b中是否包含了0,因为0不算倍数。然后需要先判断是否整除15而不是先判断是否整除3(思维惯性)。
进阶问题主要考察的是是否做过筛数法(一种求质数的方法)和是否对堆有一定了解。思路是:假如而除数很大,那么有很多数实际上是不会被任何除数整除的,但这些数有都需要尝试去除以每个除数,这样耗费就很大。所以采用“逆向思维”,尝试去构造出整个答案序列,而不是每个每一个可能的答案再判断是不是答案。既然构造的话,就想到,对于每个除数,实际上他代表了一个数列:[3, 6, 9, 12 …] 和 [5, 10, 15, 20 …] 你要做的工作实际上是合并两个有序序列。除数有m个的话,就是合并m个有序序列,这个自然就想到了多路归并算法,用堆来实现。