題目出處html
最後一道附加題,當時搞來搞去的,沒有搞好,昨天晚上敲了一下,今天拿來跟狄米特同窗比試了一下速度,沒想到居然是個人快了(由於我幾乎沒有比他快過)。node
題目描述:ios
求第k大的因數只有3,5和7的數。好比當k=1,2,3的時候答案應該爲3,5,7。算法
筆試題就是這個樣子,也不說k的密集程度(測試的時候會有多少個k),也不說k的範圍,搞得人很糾結數組
可是題上面說了,要求時間複雜度最小。數據結構
個人分析和解法:ide
咱們假設k的範圍爲1-n。那麼對於其中的每個解,若是時間最優的話,最快多是O(1),那麼對於整個k的範圍,能夠採用O(n)的方法進行預處理,以後對於每個k進行O(1)的輸出。測試
關鍵的思想在於對於任意一個知足條件的數字必定是由以前的某個數字*3 、*5或者*7獲得的。那麼就能夠用相似動態規劃的思想從3,5,7開始向後更新。取3,5,7三個指針,每次用數字去乘以當前指針所指的數字(要求是這個數字以前沒有出現過好比3*5 = 5*3這樣會獲得相同的結果),比較以後獲得最小的,更新結果數組和指針。spa
設置ans[]用來存放結果。3d
p[3]表示3,5,7對應的指針(從工程的角度講應該這樣寫吧)。
如此這般看來,關鍵的問題就在於如何判斷重複的狀況。
判斷重複能夠用的方法有:
遍歷一邊以前的表,看是否存在重複的結果(這樣的話,算法的複雜度就會退化到O(n*n),由於每個數字都須要遍歷。),很差。
第二種方法,是這樣,開一個足夠大的數組,進行標記,每次查找某個數字x是否有重複時候就看mark[x]是否爲0,若是不爲0,那麼證實這個數字能夠用,而且將其標記爲1。這種方法,看似不錯,可是仍是有一些問題。首先,當n比較大的時候,這個數組須要開十分大,纔有可能包含全部的結果。其次,這個數組是稀疏的數組,有不少空間會被浪費(由於只有3,5,7的倍數被標記了)。因此這個辦法也不是很可取。
第三種方法,開一個三維數組mark[i][j][k]進行標記。三個維度分別對應某個數字的因數中3,5,7的個數。對於獲得的一個數字x,將其分解爲3^i*5^j*7^k,若是這個位置的mark爲0證實這個數字可用,而且將其標記爲1。這種記錄方法,很大程度上解決了空間利用率低的問題,由於每個位置都有可能會被標記。那麼因而又有了一個問題,就是 三維數組須要多大的空間?這要取決於n的範圍,可是咱們能夠對比一下兩種標記方式能夠表示的數字的範圍。假設空間爲10^6,那麼對於上一種方案,能標記的最大數字就是10^6,對於這個方法,咱們每個維度均可以保存到100,那麼這個最大的數字就是3^100*5^100*7^100,看看有多大就知道啦(unsigned long long 都沒他大了)。
剩下的就是如何獲得一個數字對應的3,5,7的因數的個數問題了。
若是每次都直接分解,那麼算法複雜度就退化爲O(n*log(n))了,顯然不夠給力。
因爲每個數字(x)都是由前一個數字(y)*3,*5,*7得來的,那麼也就是說對於x來講也就是y相應的質因數+1就好(*3的話就在y的3的因數個數+1)。這樣,這個問題也解決了,就是在存result的時候也應該把對應數字的因數個數存下來。代價不大能夠接受!
貼下個人狗血的代碼好了:
1 #include<iostream> 2 #include<cstring> 3 #include<ctime> 4 using namespace std; 5 6 struct Node{//保存結果的數據結構 7 unsigned long long num; 8 int n[3]; 9 Node(unsigned long long _num = 0, int _n3 = 0, int _n5 = 0, int _n7 = 0){ 10 num = _num; 11 n[0] = _n3; 12 n[1] = _n5; 13 n[2] = _n7; 14 } 15 }; 16 17 const int MAXK = 4000+5;//k的範圍 18 19 const int MAXN = 500+2;//標記數組的範圍 20 21 Node ans[MAXK] = {Node(3,1,0,0),Node(5,0,1,0),Node(7,0,0,1)}; 22 23 int mark[MAXN][MAXN][MAXN]; 24 25 int p[3]= {0,0,0}; 26 27 unsigned long long clc[3] = {3,5,7}; 28 29 30 Node get_num(int x){//獲得下標爲x的指針的下一個數字 31 Node next = ans[p[x]]; 32 next.num = next.num*clc[x]; 33 next.n[x]++; 34 if ( mark[next.n[0]][next.n[1]][next.n[2]] == 0 ){ 35 return next; 36 } 37 return Node(-1,0,0,0); 38 } 39 40 void Mark(Node node){//標記mark 41 mark[node.n[0]][node.n[1]][node.n[2]] = 1; 42 return; 43 } 44 45 Node get_next(){//獲得下一個ans 46 int i = 0; 47 int m = -1; 48 Node Min(-1,0,0,0); 49 int M[3]; 50 while (m == -1 ){ 51 for ( i = 0; i < 3; i++ ){ 52 Node next = get_num(i); 53 while ( next.num == -1 ){ 54 p[i]++; 55 next = get_num(i); 56 } 57 if ( ( next.num != -1 ) && ( ( -1 == m ) || ( next.num < Min.num ) ) ){ 58 m = i; 59 Min = next; 60 } 61 } 62 } 63 p[m]++; 64 Mark(Min); 65 return Min; 66 } 67 68 void pre_process(){//打表 69 int i; 70 i = 3; 71 memset(mark,0,sizeof(mark)); 72 for ( ; i < MAXK; i++ ){ 73 ans[i] = get_next(); 74 } 75 } 76 77 void show(){//輸出所有結果 78 for ( int i = 0; i < MAXK; i++ ){ 79 cout<<ans[i].num<<endl; 80 } 81 } 82 83 int main(){ 84 clock_t start,finish; 85 start=clock(); 86 pre_process(); 87 show(); 88 finish=clock(); 89 cout<<(double)(finish-start)/CLOCKS_PER_SEC; 90 }
這一個題目,在考場上沒有想那麼仔細,就是想到開個三維數組標記一下,而後打表。過後仔細想想,仍是有點意思的,真沒想到這個想法的理論依據仍是有的。無奈考試的時候不懂得考試的技巧上去就寫代碼,最後還劃掉了從新寫了一下思想,但確定沒有如今整理的清楚了。但願明年去筆試可以給力吧。