一道google面試題--自然數e中出現的連續的第一個10個數字組成的質數


博客內容移到 http://www.linuxyu.com/

此CSDN博客將不再更新,歡迎大家訪問新的網站~~


Google早幾年在美國很多地鐵的出站口都有大幅招聘廣告,它的第一題如下了:{first 10-digit prime found in consecutive digits e}.com,也就是自然數e中出現的連續的第一個10個數字組成的質數。據說當時只要正確解答了這道題,在瀏覽器的地址欄中輸入這個答案,就可以進入下一輪的測試,整個測試過程如同一個數學迷宮,直到你成爲google的一員。

對這題比較感興趣,就暫時開題,這兩天動筆完成這篇博客。

難點分析

初步分析,主要兩個難點:

(1)e的小數點後n位有效數字,提供素數判斷的數據源;

(2)素數的高效判斷,尤其時針對10位數的指數判斷。

自然數e計算

計算e可使用泰勒公式推出來的

e^x1 + x + x^2/2! + x^3/3! +……+ x^n/n!  1

當x=1時,

e1 + 1 + 1/2! + 1/3! +……+ 1/n!   2

取n=10,即可算出近似值e≈2.7182818。 當n取較大值時,可獲得高精度的e。

版本1

最初步的想法,直接利用泰勒公式計算,printf出來。

//compute e 
//program 1

#include <stdio.h>
#define N 20

int main()
{
    double e = 1.0, t = 1.0;
    int i = 0;

    for(i = 1; i <= N; i++){
        t = t / i;
        e = e + t;
    }

    printf ("e=%.20lf\n",e );
    return 0;
}

版本2

對(2)進行疊乘合併起來,改寫成如下(3):

e=2+1/2(1+1/3(1+1/4(1+1/(n-1)(1+1/n))))   3

從最裏面的括號往外算,共做n次除法和加法得一段結果,運算效率也是O(N*M),但是由於收斂速度快些。

//compute e 
//program 2

#include <stdio.h>
#define N 20

int main()
{
    double e = 0.0;
    int i = 0;

    for(i = N; i > 0; i--){
        e = (e + 1.0) / i;
    }

    printf ("e=%.20lf\n",e );
    return 0;
}

版本3

版本1和2只能達到小數點後15位精度,假設我們需要的數據源是e達到e後面1000位數,那麼就必須要考慮到高精度的問題。自定義數據結構時必須的,但(3)中主要計算加法、減法和乘法不會丟失精度,而除法會丟失精度,因爲它始終存在餘數,餘數是丟失精度的原因,所以要保存精度就是保存餘數,在這裏的除法將產生兩個整數,一個是商,一個是餘數,形如:

a / b = (d,r)

如果以10爲基,那高精度就是對這樣的除法繼續做下去,將r*10恢復精度,再除,直到滿足精度爲止。

//compute e 
//program 3

#include <stdio.h>

//umber of significant digit=(DN-1)*4
#define DN 2504

int euler[DN], data[DN];
int step,n;

void precise_division()
{
    int i=0;
    long y=0,r=0;

    for(i=step;i<DN;i++)
    {
        y = 10000*r + data[i];
        data[i] = y/n;
        euler[i] += data[i];
        r = y%n;
    }
}

void revise()
{
    int c=0,i=0;

    for(i=DN-1;i>=0;i--)
    {
        euler[i] += c;
        if(euler[i]>9999)
        {
            c = euler[i]/10000;
            euler[i] = euler[i]%10000;
        }
        else
            c = 0;
    }
}

void euler_print()
{
    int i=0;

    printf("\n\nE=%d.\n",euler[0]);
    for(i=1;i<DN;i++)
    {
        printf("%.4d ",euler[i]);
        if((i%250)==0)
            printf("\n\tnow it has %d bit\n",i*4);
    }
    printf("\n\neulur number is ok\n");
}

void main()
{
    int i=0;
    step=0;
    n=1;

    for(i=0;i<DN;i++)
    {
        euler[i]=0;
        data[i]=0;
    }
    euler[0]=1;
    data[0]=1;

    while(1)
    {
        i=step;
        while(data[i]==0)
        {
            i++;
            if(i>=DN)
            goto _EXT;
        }

        step=i;
        precise_division();
        revise();
        n++;
    }

_EXT:
    euler_print();
}

版本4

參考文獻2,實現本版本,唯一沒有想通的部分在於GetLength函數實現的數學原理,這個後續再補充。

//compute e 
//program 4

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//umber of significant digit
#define N 10000

typedef unsigned int UINT;

int GetLength(int m)
{
    int n=1;
    double x=2.0/2.72;
    while(m)
    {
        while(x<1.0)
        {
            n++;
            x*=(double)(n+1);
        }
        while(x>=1.0 && m)
        {
            x/=10.0;
            --m;
        }
    }
    while(x<1.0)
    {
        n++;
        x*=(double)(n+1);
    }
    return n;
}

void main()
{
    const UINT base=1000000;
    int i,j,k,m;
    UINT *r=NULL;
    UINT *euler=NULL;
    UINT y=0;

    m=GetLength(N);

    r=(UINT *)calloc(m+1,sizeof(UINT));
    euler=(UINT *)calloc(N,sizeof(UINT));

    assert(r!=NULL);
    assert(euler!=NULL);

    for(i=0;i<=m;++i)
    {
        r[i]=1;
        euler[i]=0;
    }

    j=1;
    euler[0] = 2;

    for(k=N;k>0;k-=6)
    {
        y=0;
        for(i=m;i>=2;--i)
        {
            y = y + r[i]*base;
            r[i] = y%i;
            y /= i;
        }

        if(k<6)
        {
            euler[j++] = y%base;
        }
        else
        {
            if(y<base)
                euler[j++] = y;
            else
            {
                if(r)
                    free(r);
                return;
            }
        }
    }
    if(r)
        free(r);

    printf("\n\nE=%d.\n",euler[0]);
    for(i=1;i<j;i++)
    {
        printf("%.6d ",euler[i]);
        if((i%100)==0)
            printf("\n\tnow it has %d bit\n",i*6);
    }
    printf("\n\neulur number is ok\n");

    if(euler)
        free(euler);

    return;
}

素數判斷

版本1

素數,即除1以外,只能被1和其本身整除的數。因此根據定義,最初的判斷素數的方法爲版本1。

//judge prime 
//program 1
bool prime_1(int n)
{
    int i;
    for(i=2;i<n;i++)
    {
        if(n%i==0)
        {
            return false;
        }
    }
    return true;
}

版本2

由於一個數的因式分解可表示爲x = a * b, a<=b,因此在判斷整數n是不是素數時並不需要整數從2 ~ n,只需要整除2 ~ sqrt(n) 即可。

//judge prime 
//program 2
bool prime_2(int n)
{
    int i,n2;

    n2=sqrt(n);
    for(i=2;i<=n2;i++)
    {
        if(n%i==0)
        {
            return false;
        }
    }
    return true;
}

但是需要注意的是,int類型只能表示2^-31 ~ 2^31 範圍,並不能完整表示10位大小的整數n,因此需要使用能夠long long int類型。其次,求平方根的函數原型爲double sqrt(double),進行sqrt(n)計算時並不確定能保證其精度。最後,對e小數點後,每個10位數字大小的整數n進行判斷,需要對每個2 ~ sqrt(n)的整數進行判斷,單個判斷耗時間太久,所有判斷重複運算太多。因此次方法必然不行。

版本3

任何一個合數都可以表現爲適當個素數的乘積的形式,所以我們只用素數去除要判斷的數即可,比如要判斷100以內的素數,只用10以內的2,3,5,7就夠了。那麼首先就要構建一個素數表,以空間換時間。

可以使用一種被稱爲“埃拉托色尼篩”的算法。該算法一開始初始化一個2~n的連續整數序列。從2開始,2的倍數肯定不是素數,去除,剩下的數中下一個是3。再把3的倍數去除,再下一個是5(4已經去除了),以此類推,最後剩下的數必然是素數,因爲它沒有被篩,不是任何一個數的倍數(除了1和自己)。這樣只需要很多次加法就可以了。

//create prime less then n 
//program 3

#include <stdio.h>
#include <stdlib.h>
#include <math.h>  //Linux, gcc -lm
#include <stdbool.h>


#define Long64 long long
#define MAX_N 8000000

Long64 prime_table[MAX_N/2]={0};

void create_prime_table(Long64 n)
{
    Long64 i,j,n2;
    bool prime[MAX_N+1]={0};

    n2=sqrt(n);

    i=2;
    for(i=2;i<=n2;i++)
    {
        if(prime[i]==0)
        {
            for(j=i+i;j<=n;j+=i)
            {
                prime[j]=1;
            }
        }
    }

    j=0;
    for(i=2;i<=n;i++)
    {
        if(prime[i]==0)
        {
            prime_table[j]=i;
            j++;
            printf("%lld : %lld \n",j,i);
        }
    }
}

void main()
{
    create_prime_table(MAX_N);
}

這算是基本的篩法了,但它還有很多優化的空間。首先內存中存的表不只是素數,還有合數的空間,合數比素數多得多。 其次對於合數可能會被篩選多次,比如6,在2的時候篩掉一次。在3的時候又篩掉一次。

版本4

採用線性篩選方式,即對一個合數,只進行一次篩選,極大程度的減少重複計算。 同時,採用堆結構存放素數表。

//create prime less then n
//program 4

#include <stdio.h>
#include <stdlib.h>
#include <math.h>  //Linux, gcc -lm
#include <stdbool.h>


#define Long64 long long
#define MAX_N 1000000000

void create_prime_table2(Long64 n)
{
    Long64 i,j,k;
    bool *prime;
    Long64 *prime_table;

    prime=(bool *)calloc(MAX_N+1,sizeof(bool));
    prime_table=(Long64 *)calloc(MAX_N/100,sizeof(Long64));

    i=2;
    k=0;
    for(i=2;i<=n;i++)
    {
        if(prime[i]==0)
        {
            prime_table[k++]=i;
            //printf("%lld : %lld \n",k,i);
        }
        for(j=0;j<k && prime_table[j]*i <= n;j++)
        {
            prime[prime_table[j]*i]=1;
            if(i%prime_table[j]==0)
                break;
        }
    }
    printf("k = %lld\n",k);

    if(prime_table)
        free(prime_table);
    if(prime)
        free(prime);
}

void main()
{
    create_prime_table2(MAX_N);
}

這樣做之後,程序運行速度快多了,但是隻能計算到1x10^9....再大時,運行就提示段錯誤了。還要再在其他地方想辦法。。。 暫時先記錄到這裏。

自然數e

上上一篇博客中,雖然實現了計算n爲自然數e的程序,但是GetLength 函數實現的數學原理一直沒有想清楚。其實是這樣的。 計算自然數e時,我們使用的時泰勒公式

e^x = 1 + x + x^2/2! +... + x^n/n!...

當考慮到泰勒公式的餘項時,餘項可以表示爲

R(x) = (e^esp * x^(n+1))/(n+1)!

其中esp在0~x之間。再取x=1,得到

R(1) = e^esp/(n+1)! <= e/(n+1)! < 10^(-M)

10^(-M)就是我們要達到的誤差限,也就是小數點後的後M位。 因此,若要計算到10000位的精度,只需計算(n+1)!/e > 10^10000即可。 程序中GetLength的實現原理就是這樣。

素數判斷

上一篇博客中,只計算到1x10^9內的素數,並沒有達到要求,主要時卡在了數據結構上,再大的話在我2G內存32位系統上就已經提示段錯誤了。現在回過頭了想想,其實還可以在算法上再改進。 我們知道a*b = c,一個M位的數乘上另外一個M位的數,得到結果位數範圍在M+1~2M的範圍內。我們只要求出5位數以內的素數,就可以用來判斷10位數的整數是否爲素數了。 那麼素數判斷的程序就可以修改成這樣,也就是版本2和版本4的結合。

版本5

#include <stdio.h>
#include <stdlib.h>
#include <math.h>  //Linux, gcc -lm
#include <stdbool.h>

#define Long64 long long
#define MAX_N 100000

//create prime less then n 
//program 5

Long64 create_prime_table3(bool *prime, Long64 *prime_table, Long64 n)
{
    Long64 i,j,k;

    i=2;
    k=0;
    for(i=2;i<=n;i++)
    {
        if(prime[i]==0)
        {
            prime_table[k++]=i;
            //printf("%lld : %lld \n",k,i);
        }
        for(j=0;j<k && prime_table[j]*i <= n;j++)
        {
            prime[prime_table[j]*i]=1;
            if(i%prime_table[j]==0)
                break;
        }
    }

    return k;
}

bool prime3(Long64 prime_table[], Long64 length, Long64 n)
{
    Long64 i;

    for(i=0;i<length;i++)
    {
        if(n%(prime_table[i])==0)
        {
            return false;
        }
    }
    return true;
}

void main()
{   
    bool *prime;
    Long64 *prime_table,i=0,j=0,length=0;

    prime=(bool *)calloc(MAX_N+1,sizeof(bool));
    prime_table=(Long64 *)calloc(MAX_N,sizeof(Long64));

    length = create_prime_table3(prime,prime_table,MAX_N);

    for(i=1000000000; i<10000000000; i++)
        if(prime3(prime_table,length,i)!=false)
            printf("%lld : %lld\n",j++,i);
}

收尾:判斷e中首個連續10位素數

有了以上問題解決後,這道題終於可以解決了。不僅可以找到第一個10位長度的素數,還可以找到前10個!前10個結果如下,格式爲“第幾個:在e中的起始位置:要找的素數”

1:99:7427466391 
2:123:7413596629 
3:149:6059563073 
4:171:3490763233 
5:182:2988075319 
6:201:1573834187 
7:214:7021540891 
8:218:5408914993 
9:254:6480016847 
10:295:9920695517

完整實現代碼如下

#include <stdio.h>
#include <stdlib.h>
#include <math.h>  //Linux, gcc -lm
#include <stdbool.h>
#include <assert.h>

#define MAX_E_BITE_N 1000
#define PRIME_N 1000000

#define Long64 long long

typedef unsigned int UINT;


int get_length(int nbite)
{
    int m = 1;
    double x = 2.0/2.72;
    while(nbite)
    {
        while(x<1.0)
        {
            m++;
            x *= (double)(m+1);
        }
        while(x >= 1.0 && nbite)
        {
            x /= 10.0;
            --nbite;
        }
    }
    while(x < 1.0)
    {
        m++;
        x *= (double)(m+1);
    }
    return m;
}


int create_e(Long64 *euler, const UINT base, int m)
{
    UINT *r = NULL;
    UINT y = 0;
    int i = 0,j = 0,k = 0;

    r=(UINT *)calloc(m+1,sizeof(UINT));

    assert(r != NULL);
    assert(euler != NULL);

    for(i = 0;i <= m; ++i)
    {
        r[i] = 1;
        euler[i] = 0;
    }

    j = 1;
    euler[0] = 2;

    for(k = MAX_E_BITE_N; k > 0; k -= 5)
    {
        y = 0;
        for(i = m; i >= 2; --i)
        {
            y = y + r[i]*base;
            r[i] = y%i;
            y /= i;
        }

        if(k < 5)
        {
            euler[j++] = y%base;
        }
        else
        {
            if(y < base)
                euler[j++] = y;
            else
            {
                if(r)
                    free(r);
                return 0;
            }
        }
    }
    if(r)
        free(r);

    printf("\n\nE=%lld.\n",euler[0]);
    for(i = 1; i < j; i++)
    {
        printf("%.5lld ",euler[i]);
    }
    printf("\n\neulur number is ok j=%d\n",j);

    return j;
}

Long64 create_prime_table(bool *prime, Long64 *prime_table, Long64 n)
{
    Long64 i,j,k;

    i = 2;
    k = 0;
    for(i = 2;i <= n;i++)
    {
        if(prime[i] == 0)
        {
            prime_table[k++] = i;
//            printf("%lld : %lld \n",k,i);
        }
        for(j = 0; j < k && (prime_table[j]*i) <= n; j++)
        {
            prime[prime_table[j] * i] = 1;
            if(i%prime_table[j] == 0)
                break;
        }
    }

    return k;
}

bool is_prime(Long64 prime_table[], Long64 length, Long64 n)
{
    Long64 i;

    for(i = 0; i < length; i++)
    {
        if(n%(prime_table[i]) == 0)
        {
            return false;
        }
    }
    return true;
}


void main()
{
    const UINT base = 100000;
    int m = 0,elength = 0,j = 0,find_number = 10;

    Long64 *euler = NULL;
    bool *prime = NULL;
    Long64 *prime_table = NULL;

    Long64 i = 0,length = 0,data = 0;

    Long64 ten[10] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};

    m = get_length(MAX_E_BITE_N);

    euler = (Long64 *)calloc(MAX_E_BITE_N,sizeof(Long64));

    elength = create_e(euler,base,m);


    prime = (bool *)calloc(PRIME_N+1,sizeof(bool));
    prime_table = (Long64 *)calloc(PRIME_N,sizeof(Long64));

    length = create_prime_table(prime,prime_table,PRIME_N);

    if(prime)
        free(prime);

    m = 0;
    for(i = 1; (i+2) < elength; i++)
    {
        for(j = 0; j < 5; j++)
        {
            data = euler[i]%ten[5-j]*ten[5+j] + euler[i+1] * ten[j] + euler[i+2]/ten[5-j];

//          printf("data : %lld \n",data);

            if(is_prime(prime_table,length,data) == true)
            {
                printf("find it! %d:%lld:%lld\n",++m,5*(i-1)+j+1,data);

                if(m == find_number)
                    break;    
            }               
        }

        if(m == find_number)
            break;    
    }


    if(euler)
        free(euler);
    if(prime_table)
        free(prime_table);

    return;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章