博客斷更了一段時間,現在準備重新撿起來。寫博客是一個總結,分享的過程,也是提升自我表達的很好的方式。將一個問題以深入淺出的方式表達出來,讓讀者讀的開心,容易理解,這是我們開發人員職業生涯當中很重要的一個能力。
前一陣子秋招,筆試面試了挺多公司,也積累了一點點自己的經驗,想陸續分享出來,也請各位多多斧正。
第一家面試的是深圳一家遊戲公司,手寫算法題,求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;
}
機器配置,