谷歌的招聘题

谷歌的招聘题

旧金山湾地区的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等每一个十位数,看看是否为质数。

会有人使用素数代替质数,但两数只有互质。

 

  1. 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)
  1. 计算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)
  1. 商结果

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

 

  1. 求质数的结果

对于前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

……

 

  1. 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

 

  1. 讨论

如果我们一开始就上网查,找到【3】,虽然不知真假,但可以上网试呀。所以程序员有“最好的一个习惯,放到最后压轴吧。善用Google和知乎”。但是也要会查,看到【1】,能查到【2】吗?

如果查不到,或者查到的没说明白,就需要自己解决。如果不会python,估计编程时间变长。Python语言似乎什么都能做。在数字签名中,也是需要计算很长的数,以256位长度打底。

 

  1. 截断误差 

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

 

  1. 参考文献
  1. 招聘还可以这么玩?果然无趣限制了我的想象。
  2. http://explorepdx.com/firsten.html
  3. 跪求自然对数的底e=2.718281828459045以后的100万数值或有数值100万的网站。急用,在此谢过大侠了!
  4. http://explorepdx.com/firsten.html

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值