有道難題

有道難題的初賽已經結束了,貧道也第一次玩了一下這種測試,剛做完時很興奮,成績卻很不好。有道難題看似簡單,其實不易,經此一測,貧道發現自己離大道還甚遠。
題目:


請注意,在上面的僞代碼中,A[i]和B[i]分別表示字符串A和B中下標爲i的字符(下標編號從0開始)。對“完全平方數”的定義是,對於整數i,存在整數j,使得i= j *j,則稱i爲完全平方數。
下面具體說明序列生成的過程:如果n=7,則在每一輪迭代中,A的取值依次爲:0, 01, 0110, 01101010,所以最終產生的二進制序列就是0,1,1,0,1,0,1,0
請返回上述序列中下標爲n的數字(該序列的下標從0開始)
類及函數定義:
Class:BinarySequence
Method signature:int getValue(int n)
(be sure your method is public)
要求:
n 的取值大於等於0,小於等於2,000,000,000
輸入->輸出:7->0  2->1  8->1  11->0  10->1  15->0
開解:
貧道剛看到此題,竊喜不已:何等簡單啊,算法的僞碼都給出來了,簡直就是不煩腦細胞吧。於是貧道十指連發,打下了如下爪哇咒語:


再拿要求裏的輸入輸出一驗證,無量天尊,貌似是對的;藐視之餘,貧道拿分後飄然而去......
直到第二天被板磚拍回來。
據說系統拿2,000,000,000來測試貧道的程序,由於舉辦方機器不夠強勁,所以沒有通過;鑑於解釋權在彼方,貧道只能捏着牛鼻子配合對程序進行優化優化。
既然是僞碼的一致性實現,那麼正確率應該問題不大,那先看一下程序的效率吧:
輸入(耗時):2000(15)  20000(1704) 40000(6859)  80000(41750) 200000(zzZZZZZZZZZZZ)

 

看來程序確實有那麼點問題,要動動手術才行。
修改1:判斷是否完全平方數的函數isSquare實在太圡了,聽說可以使用系統函數,自然想到了Java的Math類。

再看看效率:2000(16) 20000(750) 40000(3812) 80000(28188) 200000(240703) 2000000(zZZZZZZZZZZZZZ);雖然提高了一倍,但還是不盡人意。
Math.sqrt()還能優化嗎,找找Java的源代碼,Math.sqrt調用的是StrictMath.sqrt(),而後者是一個native方法,也就是說用其他語言在底層實現了,那就先這樣吧。

修改2:看看僞碼的實現getValue函數吧,中間有頻繁的字符串變更,而String對象又是不可變的,那麼顯然用StringBuffer會好一些吧。把A和B都改成StringBuffer類型再看看效率:
2000(0) 20000(15) 40000(32) 80000(63) 200000(93) 2000000(672),5000000(2656).......不錯,效率提高了數個量級,但是再增大n,取到10000000的時候,貧道的Eclipse報了一個OutOfMemoryError;一咬牙把運行參數上加了一個 -mx800M n能增大一些,但是大不了多少就又zZZZZZZZZZZZZZZZZZZZZ,三清祖師,看來還需要再優化。

修改3:再優化一下僞碼算法的實現,考慮減少計算,提高已有計算結果的複用度。
已知:如果n=7,則在每一輪迭代中,A的取值依次爲:0, 01, 0110, 01101010。
每一次迭代都是從A的第一個字符算起,A的前一半字符在上一次迭代中都計算過了,並且把計算的結果B追加在了A的後面,也就是說:上n次結果中的B之和在本次中可以複用(直接追加),能夠減少一半的計算量。
在這種想法下,修改getValue,得到如下代碼:

再來看看效率:2000(0) 20000(0) 40000(15) 80000(16) 200000(31) 2000000(344),5000000(641),10000000(1265)。確實提高了一倍,也突破了10000000這個坎,可再增大n至20000000時又報了OutOfMemoryError。StringBuffer雖然沒有長度限制,但是其大小也是與jvm相關的,太大了會內存不夠。看來得在減少StringBuffer長度上做些文章了。
修改4:回頭再看看僞碼和要求,最後要求輸出的是第n位字符而不是全部,那麼可否直接算n而忽略中間過程呢,n又有什麼規律呢?
n=0 輸出0
n=1 輸出1
n=2,4,8.......由於字符串每次都是成倍增長,所以當n爲2的冪時,必然是由第一個字符'0'生成,也就是說輸出一定是1
如果n不是2的冪時,它會夾在兩個2的冪中間,設爲k<n<m,那麼n就是由位於(n-k)的字符變化而來,所以可以先得到(n-k),再取n。
於是將getValue寫成了這樣的形式:

public int getValue(int n)仍舊採用前面的。效率提高了:2000000(203) 20000000(453)
特別是如果n在k的附近,那就非常快了,但是n遠離k的時候呢,(n-k)有可能還是上千萬,就還是會陷入getValue的OutOfMemoryError。
修改5:接前面的思路,必須減少(n-k)的計算量,把那n-k看做一個單獨的數a來計算,與n類似,a豈不是又可以用兩個2的冪來來計算?一層層迭代,這是什麼?對了,迭代。
於是修改getvalue爲迭代函數:

看看效率:20000000(0) 2000000000(0),非常強大...
原來這就是道啊......
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章