博客断更了一段时间,现在准备重新捡起来。写博客是一个总结,分享的过程,也是提升自我表达的很好的方式。将一个问题以深入浅出的方式表达出来,让读者读的开心,容易理解,这是我们开发人员职业生涯当中很重要的一个能力。
前一阵子秋招,笔试面试了挺多公司,也积累了一点点自己的经验,想陆续分享出来,也请各位多多斧正。
第一家面试的是深圳一家游戏公司,手写算法题,求N以内的素数,当然需要一步步的去优化时间复杂度,过程就不细说了,下面说说我的理解。
1、首先是最常规的解法,判断n是不是素数,即判断n能否被[2,sqrt(n)]整除,若一个数不是素数,那么它分解的因数中一定有一个小于等于sqrt(n),另一个大于等于sqrt(n)。
static List<Integer> prime1(int n) { //n>=2
List<Integer> pri=new ArrayList<>();
pri.add(2);
for(int i=3;i<=n;i=i+2) {
int tmp=(int)Math.sqrt(i);
int j=2;
for(;j<=tmp;j++) {
if(i%j==0) break;
}
if(j>tmp) pri.add(i);
}
return pri;
}
2、思考发现,判断n能否被[2,sqrt(n)]整除,其中的除过2之外的偶数是多余的,即只用判断[2,sqrt(n)]之间的奇数。因为n若不能被2整除,就一定不能被2之后的偶数整除,这样可以减小判断的时间。
static List<Integer> prime2(int n){
List<Integer> pri=new ArrayList<>();
pri.add(2);
for(int i=3;i<=n;i=i+2) {
int tmp=(int)Math.sqrt(i);
int j=3; //i已限定奇数,对2求余不为0
for(;j<=tmp;j=j+2) {
if(i%j==0) break;
}
if(j>tmp) pri.add(i);
}
return pri;
}
3、继续探索,判断n能否被[2,sqrt(n)]之间的奇数整除,这些奇数里面的合数是多余的。因为任何一个合数都可以分解为它前面若干个素数的积,若不能被它前面的素数整除,则亦不能被此合数整除。所以整除的范围缩小为[2,sqrt(n)]之间的素数。刚好本题目的目的就是求素数,所以可以将求得的素数放在数组内用来判断。
static List<Integer> prime3(int n) {
List<Integer> pri=new ArrayList<>();
pri.add(2);
boolean flag=true;
for(int i=3;i<=n;i=i+2) {
flag=true;
int tmp=(int)Math.sqrt(i);
for(int j=0;j<pri.size() && pri.get(j)<=tmp;j++) {
if(i%pri.get(j)==0) {
flag=false;
break;
}
}
if(flag) pri.add(i);
}
return pri;
}
4、因为Java的ArrayList的get和add函数比起数组的存取来讲是比较耗时的,考虑是否可以使用数组代替List。如要用数组代替List,首要考虑问题开辟多大的数组比较好,在尽量节省内存的情况下可以使用素数定理。n以内素数的数量大约是n/ln(n),这个估计是小于真实值的,所以在实际使用的时候需要视情况再加几个。若n=2000000,此处选择数组大小是n/10。
static int[] prime4(int n) {
int num=n/10;
int[] pri=new int[num];
pri[0]=2;
int k=1;
boolean flag=true;
for(int i=3;i<=n;i=i+2) {
flag=true;
int tmp=(int)Math.sqrt(i);
for(int j=0;j<num && pri[j]>0 && pri[j]<=tmp;j++) {
if(i%pri[j]==0) {
flag=false;
break;
}
}
if(flag) {
pri[k]=i;
k++;
}
}
return pri;
}
机器配置,