面试官本想拿一道求素数搞我,但被我优雅的"回击"了

点击上方蓝字关注我程序员

前言

如今的面试官,是无数开发者的梦魇,可以吊打面试官的属实很少,由于大部分面试官真的有那么那几下子。但在面试中,咱们这些小生存者不能全盘否认只能单点突破—从某个问题上让面试官眼前一亮。这不,今天就来分享来了。web

这年头,算法岗内卷不说,开发岗也有点内卷,对开发者要求愈来愈高了,而面试官也是处心积虑的 "刁难" 面试者,凡是都喜欢由浅入深,凡是都喜欢问个:你知道为何?你知道原理吗?之类。而且,之前只是大厂面试官喜欢问算法,大厂员工底子好,不少甚至有ACM经验或者系统刷题经验,这很容易理解,但如今一些小公司面试官也是张口闭口 xx算法、xx数据结构你说说看,这不,真的被问到了。面试

求一个质数

在这么一次的过程,面试官问我算法题我不吃惊,我实现早把十大排序原理、复杂度分析、代码手写实现出来了,也把链表、树的各类操做温习的倒背如流,不过忽然就是很诧异的面试官来了一道求素数问题,我把场景还原一下:算法

面试官:你知道怎么求素数吗?数组

我:求素数?微信

面试官:是的,就是求素数。数据结构

我:这很简单啊,判断一个数为素数,那么确定就没有两个数(除了自身和1)相乘等于它,只须要枚举看看有没有可以被它整除的数就能够了,若是有那么就不是素数,若是没有,那么就是素数。app

面试官露出一种失望的表情,说我说的对,但没答到点子上,让我具体说一下。编辑器

下面开始开始个人表演:flex

首先,最笨的方法,判断n是否为素数,就是枚举[2,n-1]之间有没有直接可以被n整除的,若是有,那么返回false这个就不是素数,不然就是素数,代码以下:

boolean isprime(int value){
  for(int i=2;i<value;i++)
  {
       if(value%i==0)
       {return false;}
  }
    return true;
}

这种判断一个素数的时间复杂度为O(n).

可是其实这种太浪费时间了,彻底不必这样,能够优化一下 。若是一个数不是质数,那么一定是两个数的乘积,而这两个数一般一个大一个小,而且小的小于等于根号n,大的大于等于根号n,咱们只须要枚举小的可能范围,看看是否可以被整除,就能够判断这个数是否为素数啦。例如100=2*50=4*25=5*20=10*10 只须要找2—10这个区间便可。右侧的必定有个对应的不须要管它。

boolean isprime(int value)
{
  for(int i=2;i*i<value+1;i++)
    {
       if(value%i==0)
       {return false;}
    }
    return true;
}

这里之因此要小于value+1,就是要包含根号的状况,例如 3*3=9.要包含3.这种时间复杂度求单个数是O(logn)。面试官我给你画张图让你看看其中区别:


2

说到这里面试官露出欣慰的笑容。

面试官:不错不错,基本点掌握了

我:老哥,其实求素数精髓不在这,这个过低效在不少时候,好比求小于n的全部素数,你看看怎么搞?

面试官:用个数组用第二种方法求nlogn还行啊。

求多个素数

求多个素数的时候(小于n的素数),上面的方法就很繁琐了,由于有大量重复计算,由于 计算某个数的倍数 是否为素数的时候出现大量的重复计算,若是这个数比较大那么对空间浪费比较多。

这样,素数筛的概念就被发明和使用。筛的原理是从前日后进行一种递推、过滤排序以来统计素数。

埃拉托斯特尼(Eratosthenes)筛法

咱们看一个数若是不是为素数,那么这个数没有数的乘积能为它,那么这样咱们能够根据这个思想进行操做啊:

直接从前日后枚举,这个数位置没被标记的确定就是素数,若是这个数是素数那么将这个数的倍数标记一下(下次遍历到就不须要在计算)。若是不是素数那么就进行下一步。这样数值越大后面计算次数越少,在进行具体操做时候可借助数组进行判断。因此埃氏筛的核心思想就是将素数的倍数肯定为合数

假设刚开始全是素数,2为素数,那么2的倍数均不是素数;而后遍历到3,3的倍数标记一下;下个是5(由于4已经被标记过);一直到n-1为止。具体流程能够看图:


具体代码为:

boolean isprime[];
long prime[];
void getprime()
{
        prime=new long[100001];//记录第几个prime
      int index=0;//标记prime当前下标
        isprime=new boolean [1000001];//判断是否被标记过
        for(int i=2;i<1000001;i++)
        {
            if(!isprime[i])
            {
                prime[index++]=i;
            }
            for(int j=i+i;j<1000000;j=j+i)//他的全部倍数都over
            {
                isprime[j]=true;                    
            }
        }
}

这种筛的算法复杂度为O(nloglogn);别小瞧多的这个logn,数据量大一个log可能少很多个0,那时间也是十倍百倍甚至更多的差距。

欧拉筛

面试官已经开始点头赞同了,哦哦的叫了起来,可其实还没完。还有个线性筛—欧拉筛。观察上述的埃氏筛,有不少重复的计算,尤为是前面的素数,好比2和3的最小公倍数为6,每3次2的计算就也会遇到是3的倍数,而欧拉筛在埃氏筛的基础上改进,有效的避免了这个重复计算。

具体是何种思路呢?就是埃氏筛是遇到一个质数将它的倍数计算到底,而欧拉筛则是只用它乘以已知晓的素数的乘积进行标记,若是素数可以被整除那就中止日后标记。

在实现上一样也是用两个数组,一个存储真实有效的素数,一个用来做为标记使用。

  • 在遍历到一个数的时候,若是这个数没被标记,那么这个数存在素数的数组中,对应下标加1.

  • 无论这个数是否是素数,遍历已知素数将它和该素数的乘积值标记,若是这个素数可以被当前值i整除,那么中止操做进行下一轮。

具体实现的代码为:

boolean isprime[];
int prime[];
void getprimeoula()// 欧拉筛
{
        prime = new int[100001];// 记录第几个prime
        int index = 0;
        isprime = new boolean[1000001];
        for (int i = 2; i < 1000001; i++) {
            if (!isprime[i]) {
                prime[index++] = i;
            }
            for (int j = 0; j < index && i * prime[j] <= 100000; j++){//已知素数范围内枚举
                isprime[i * prime[j]] = true;// 标记乘积
                if (i % prime[j] == 0)
                    break;
            }
        }
}

你可能会问为啥if (i % prime[j] == 0)就要break。

若是i%prime[j]==0,那么就说明i=prime[j]*k. k为一个整数。
那么若是进行下一轮的话
i*prime[j+1]=(prime[j]*k)*prime[j+1]=prime[j]*(k*prime[j+1]) 当i=k*prime[j+1]两个位置就产生冲突重复计算啦,因此一旦遇到可以被整除的就中止。


你能够看到这个过程,6只标记12而不标记18,18被9*2标记。详细理解还须要多看看代码想一想。过程图就不画啦!欧拉的思路就是离我较近的我给它标记。欧拉筛的时间复杂度为O(n),由于每一个数只标记一次。

面试官露出一脸欣赏的表情,说了句不错,下面就是聊聊家常,让我等待下一次面试!

推荐阅读

「经历分享」这些图灵奖主原来就藏在身边
  16张图带你完全搞懂基数排序
「干货总结」程序员必知必会的十大排序算法
    花5分钟看这篇以前,你才发现你不懂RESTful
「五大经常使用算法」一文图解分治算法和思想

记得关注、我们下次再见!





点个在看你最好看




本文分享自微信公众号 - bigsai(bigsai)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索