谷歌的招聘题
旧金山湾地区的101号公路是一条繁忙的通勤公路,来往的是Adobe、苹果、应用材料、思科、易趣网、GeNeNeTeaTM、谷歌、休利特、帕卡德、信息技术、直觉、甲骨文、硅图形、太阳微系统、雅虎、A等公司的总部的员工。这些通勤者记得那张谷歌支付的大海报,上面是一道数学题,答出者可以获得谷歌面试的资格。
为此题吸引并向往谷歌者,认真答题,进而进了谷歌,彼此心照不宣,可以称为自然对数届。未答出者只能继续逍遥于谷歌之外。赞叹此题精妙,有中文文章如【1】文广为流传,因而被谷歌此题吸引者,会有人朝圣般的查原文。查到在广告牌上的原文【2】,只有一行字:
{ first 10-digit prime found in the consecutive digits of e } .com
中文为:
{在自然对数e连续的数字中第一个10位数的质数}.com
即在自然对数e=2.7182818284590……中找到第一个10位数的质数。
今天我们看一看进谷歌需要的智力。
解题步骤实际只要两步:首先要知道e=2.718281828459045……足够多的位,使得包括此质数;然后尝试7182818284、1828182845、8281828459等每一个十位数,看看是否为质数。
会有人使用素数代替质数,但两数只有互质。
- e的求解方法。
首先你要有e的足够多的位,用什么方法,第一方法上网查。中文版英文版,超过20位的找到【3】【4】。【3】中给了e的数值到小数点后1000位,并给出工具和计算方法:工具帮你找个能精确到100万位的计算器(点击可以进入),计算方法是使用泰勒级数:
e=1+1/1!+1/2!+1/3!+…………1/n!
数值如下,有所节选:
e=2.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642742746639193200305992181741359662904357290033429526059563073813232862794349076323382988075319525101901157383……。
如果你使用第99位开始的十位数,7427466391.com试一下,404代码出错。可以抢注这个网名。
我们不使用介绍的计算器,也不直接使用此泰勒级数。
我们不知道谷歌是百里挑一、千里挑一、抑或万里挑一。为保险起见,如果能计算大致精确到一万位,大概总够了。
不直接使用此泰勒级数是源于误差。用计算机实现实际世界总有误差。需要对误差进行估计。
使用n项泰勒级数,忽略了n+1、n+2等项,造成计算中的截断误差,可以证明(在最后)截断误差不超过(1/n!)/n。
如果直接使用此泰勒级数,在每项计算中都会有计算误差,每项误差相加造成累积误差,所以【3】帮你找一个100万位的计算器。但是实际未必够用,手算也太过麻烦。
我们的目标是大致精确到一万位,结合截断误差还给出真值所在的上下界。
我们使用上述泰勒级数的等价公式,等价方法没有累积误差。累积误差有时会出大问题。
我们用例子来说明等价公式,对于e的前8项为:
e = 1 + 1/1! + 1/2! + 1/3! + 1/4! + 1/5! + 1/6! + 1/7!
= 1 + (7!/1! + 7!/2! + 7!/3! + 7!/4! + 7!/5! + 7!/6! + 7!/7!)/7!
= 1 + (7*6*5*4*3*2 + 7*6*5*4*3 + 7*6*5*4 + 7*6*5 + 7*6 + 7 + 1)/ 7!
括号中前后颠倒:
e= 1 + (1 + 7 + 7*6 + 7*6*5 + 7*6*5*4 + 7*6*5*4*3 + 7*6*5*4*3*2)/ 7!
首先计算(1 + 7 + 7*6 + 7*6*5 + 7*6*5*4 + 7*6*5*4*3 + 7*6*5*4*3*2)/ 7!。分别计算分子与分母。
我们定义一个变量product,计算分子的每一项,从一开始为n,然后让它变为n(n-1)、n(n-1)、n(n-2)……。对于上面例子,会分别为7、7*6、7*6*5、7*6*5*4、7*6*5*4*3、7*6*5*4*3*2。
定义一个变量sum计算product之和,一开始为1,然后,不断加上product,这样sum就计算:
1 + n(n-1) + n(n-1)(n-2) + ……
定义一个变量factorial计算阶乘n!。
注意range(1, n)表示1、2、3、……n-1。
n = 10000 product = n multiplier = n sum = 1 factorial = n for i in range(1, n): sum += product multiplier -= 1 product *= multiplier factorial *= multiplier print('sum=', sum) print('factorial=', factorial) print('e=', 1+sum/factorial) print('len=',len(str(sum))) print('len=', len(str(factorial)))
我们n=10000位,计算结果如下:
Sum结果为下面起头的数字,共35660位: 48906762887954151002452382654345280493724618856911869445404735724564583117973221205964891713069379380835129048595438237946402491814312565866726108666069942645……。
Factorial以下面起头的35660位:
28462596809170545189064132121198688901480514017027992307941799942744113400037644437729907867577847758158840621423175288300423399401535187390524211613827161748……。
并得到
e= 2.7182818284590455
len= 35660
len= 35660
Factorial后面有2499个零。这些零都是当n为5的倍数时产生,所以5!的结果中有一个零,10!的结果中有两个零。同理15!有三个零,20!有四个零。注意25!有六个零,因为25=5*5,一下多了两个零。
作为验证,用程序数在计算阶乘factorial中的零的个数(计算结果为k=2499),然后计算factorial2=factorial/(10**k),结果发现在factorial中的零被清除干净。这说明至少factorial2的结果零的个数是正确的。
注意当n等于1000时,range(5, n+1, 5)表示5、10、15、……1000,//不是注解的开始,而是整数除法。
k = 0 for i in range(5, n+1, 5): ii = i while ii % 5 ==0: k += 1 ii //= 5 print('k=', k) factorial2 = factorial for i in range(k): factorial2 //= 10 print('factorial2=', factorial2)
- 计算e。
e = 1 + sum/factorial = (factorial + sum/factorial)
= numerator(分子) / denominator(分母) = quotient(商)
quotient (商)的求法,一位一位求,和手算除法一样,程序如下:
numerator = sum + factorial denominator = factorial quotient = 0 for i in range(n): q = numerator//denominator quotient = quotient * 10 + q numerator = (numerator - q * denominator) * 10 quotient = str(quotient) print('quotient=', quotient)
- 商结果
quotient= 271828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642742746639193200305992181741359662904357290033429526059563073813232862794349076323382988075319525101901157383418793070215408914993488416……
由于分子分母的有效位数都是35660位十进制数,所以商的有效位数会比35660少个1、2位,这是源于e的级数的快速收敛。所以语句
for I in range(n)
可以改为
for I in range(len(str(sum)))
即可计算35660长度的商。
由于前面讨论的截断误差的存在,此结果总是小于实际值。但是由于已知截断误差不大于(1/n!)/n,可以利用类似方法求,只要在求商之前加上:
sum = sum * n + 1 factorial *= n
就可求出上限值。
下面的第1列是n的值,第2列是有截断误差的值,第3列上限值,第4列是求商后的上限值的结果,表明求商有舍入误差。
2 2.5 2.75 27
3 2.666666666666667 2.7222222222222223 272
4 2.708333333333333 2.71875 2718
5 2.716666666666667 2.7183333333333333 27183
6 2.7180555555555554 2.718287037037037 271828
7 2.7182539682539684 2.71828231292517 2718282
8 2.7182787698412696 2.7182818700396822 27182818
- 求质数的结果
对于前500-1十位数,分别判别是否是质数,如是质数,打印此数与在e中的序号。
from math import sqrt def isprime(s): n = int(s) if n % 2 == 0: return False sq = int(sqrt(n)) for i in range(3, sq+1, 2): if (n % i) == 0: return False return True for i in range(500): s = quotient[i: i+10] if isprime(s) == True: print(s, i)
结果如下,在上面的quotient中以黄色标注了位置。第一个数(例如7427466391)是质数,第2个数(例如99)是在商中的序号:
7427466391 99
7413596629 123
6059563073 149
3490763233 171
2988075319 182
1573834187 201
7021540891 214
5408914993 218
6480016847 254
……
- 45万位
对于n = 10000,几秒钟即可得所有结果。
对于n = 100000,需要计算11分钟,相关结果如下:
sum以下例数字打头:
599898087511597931014705796144802686624886107274270915537491677749594510768……
factorial以下例数字打头: 282422940796034787429342157802453551847749492609122485057891808654297795090……
e= 2.7182818284590455
len= 456574
len= 456574
k= 24999
factorial2= 28242294079603478742934215780245355184774949260912248505789180865429779509010630178725517714138311
quotient= 2718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466391932003059921817413596629043572900334295260595630738132328627943490763233829880753195251019011573834187930702154……
7427466391 99
7413596629 123
6059563073 149
3490763233 171
- 讨论
如果我们一开始就上网查,找到【3】,虽然不知真假,但可以上网试呀。所以程序员有“最好的一个习惯,放到最后压轴吧。善用Google和知乎”。但是也要会查,看到【1】,能查到【2】吗?
如果查不到,或者查到的没说明白,就需要自己解决。如果不会python,估计编程时间变长。Python语言似乎什么都能做。在数字签名中,也是需要计算很长的数,以256位长度打底。
- 截断误差
e = 1 + 1/1! + 1/2! + …… + 1/n! + 1/(n+1)! + 1/(n+2)!+ ……
< 1 + 1/1! + 1/2! …… + 1/n! + 1/(n+1)! + 1/(n+1)!/(n+1) + ……
= 1 + 1/1! + 1/2! …… + 1/n! + 1/(n+1)![ 1 + 1/(n+1) + 1/(n+1)/(n+1)+ ……
= 1 + 1/1! + 1/2! …… + 1/n! + 1/(n+1)![ 1/(1 - 1/(n+1) )]
= 1 + 1/1! + 1/2! …… + 1/n! + [1/(n+1)!] *(n+1)/n
= 1 + 1/1! + 1/2! …… + 1/n! + (1/n!)/n
- 参考文献
- 招聘还可以这么玩?果然无趣限制了我的想象。
- http://explorepdx.com/firsten.html
- 跪求自然对数的底e=2.718281828459045以后的100万数值或有数值100万的网站。急用,在此谢过大侠了!
- http://explorepdx.com/firsten.html