解一道題


    今天下午上班做的突然很煩,一個東西搞了快兩個月了,精度沒什么進展有點煩躁。趙堅給我說了一道題目,好像是哪個公司的面試題,偷偷做一下,放松一下。題目是這樣的:一個台階一共50個階梯,從底部開始,每一步可以走1或2或3個階梯,走到頂一共有多少總走法。

    這個題目第一時間想到的是對每一步分情況討論:

 

    用回溯法做,這其實就是一個三叉樹的先序遍歷。用遞歸實現很簡單,但是這個是指數增長,而且樹的深度比較深,都是1的話要50步,最少也要3×16+2×1共17步。也就是樹的深度在17到50之間也就是在[3^17,3^50]=[129140163, 7.1790e+023]之間。果然跑了半天沒結果。我修改了一下數據,當台階數改為32時就已經開始要等待幾秒了。用這個方法在我有生之年是得不到答案的。

    於是想從排列組合入手簡化,現在的方法是給出排列,可不可以先找出組合數。也就是如果台階是共5梯,1112,1121,1211,2111是4種排列,但是我可以先找到3個1,1個2這個組合,再考慮這個組合有多少種排列方法。這樣的話節點數就會少掉很多,樹畫出來就應該變成:

 

    也就是只有1下面才有1,2,3三個節點,2下面只有2,3兩個節點,3下面只有3一個節點。這樣可以保證每條路徑得到的是一種不同的組合。先計算一下第n層的節點數。觀察一下可以看到每一層只有1個1,第n層有n個2,第1層1個3,第n層3的個數是第n-1層1的個數+2的個數+3的個數。設第n層有Sn個3,則S1=1,Sn=1+(n-1)+Sn-1.得到Sn=n(n+1)/2.所以第n層節點數是1+n+Sn=(n^2+3n+2)/2。而原來第n層的節點數是3^n。一下子從指數增長減低到了n的平方級。樹的深度仍然是在17到50之間,但是節點數減少為在 [171, 1326]之間。這個數量級,一瞬間有哪些組合就出來了。

    但是在每個節點上有多少種排列,也就是說有a個1,b個2,c個3有多少種排列,我本來想當然覺得這個很簡單,認真一想一時給不出直接的關於a,b,c的直接解,但是可以很容易想到遞歸的方法,如果說有f(a,b,c)種排列,如果第一步走1個台階,后面就有a-1個1,b個2,c個3;如果第一步走2個台階,后面就有a個1,b-1個2,c個3;如果第一步走3個台階,后面就有a個1,b個2,c-1個3.所以f(a,b,c)= f(a-1,b,c) +f(a,b-1,c) +f(a,b,c-1),f(0,0,1)=f(0,1,0)=f(1,0,0)=1.但是這樣求的話又變成一個三叉數,又變成3^n復雜度的問題了。但是很容易發現在f(a,b,c)中a,b,c是等價關系,也就是說f(a,b,c)=f(a,c,b)=f(c,a,b)。所以又變成從排列變成組合的一個過程。每次迭代之前先把f(a,b,c)的a,b,c按照升序或者降序排列。而且f(a,b,c)不用每次算,算一次保存下來,以后只要查表就可以了。這樣算法稍稍復雜了一點,但是代碼仍然很好寫,而且復雜度是n的平方,n最大只有50,每個f(a,b,c)也只需要計算一次即可,因此前面第一步得到的組合再轉換成排列,每個節點所花費的時間就幾乎是常數的。但是要先注意一個問題,排列數可能很大,很可能溢出。檢驗一下,很明顯f(a,b,c)當a,b,c大小接近的時候f(a,b,c)更大一些。50個台階a,b,c大小接近的解比如7個1,8個2,9個3.如果用unsigned long,果然f(7,8,9)溢出,改用UINT64就可以了。

    具體結果的數值不記得了,代碼在公司不能帶出來。把思路記錄一下,這個f(a,b,c)肯定有方法直接得到,不用遞歸來做。趙堅說這題目有nlogn的解法,以后再想。

 

(續)2012-05-30

    今天上午上廁所的時候又想了一下,突然想到根本沒必要這么繁瑣,我們要的只是走法的次數,具體的走法根本沒必要知道。所以應該這么思考,50梯台階可以由走了49梯台階再走1梯達到,或者走了48梯台階再走2梯達到,或者走了47梯台階再走3梯達到。所以設n梯台階有g(n)種走法。g(50)=g(49)+g(48)+g(47),更一般的g(n)=g(n-1)+g(n-2)+g(n-3),g(1)=1,g(2)=2,g(3)=4.所以計算量變成了O(n),一個for循環就搞定了。

    博客網果然高手如雲,立刻就有人指出了這種做法,而且也有人解答了昨天f(a,b,c)如何直接由a,b,c得到:f(a,b,c)=(a+b+c)!/(a!b!c!),我原來還以為要用組合數學中的生成函數來做才能搞出來。還有人指出了我計算復雜度的錯誤:我計算的是第n層節點數,而不是我這個問題計算過程中遍歷的節點數,“上述方法,由於使用了字典類,因此遞歸算法的復雜度就是n。算上總字典中查值(假設用的是二分查找)的復雜度是logn那么乘起來正好是nlogn”所以第二種計算組合數的方法並不是O(n^2)而是O(nlogn)。同理,第一種方法其實也不是O(3^n),確切應該是介於O(3^n)O(3^(n/3))之間,但是還是指數級的增長。

    這倒題其實不難,但是這個過程比較有意思,從指數級到平方級到線性級,而且如果有興趣,還可以利用生成函數,類似fibonacci數列的做法,直接得到g(n)關於n的表達式,就直接降到常量級了。不同的復雜度解放可以看到是因為中間過程得到了很多冗余結果:指數級復雜度得到了所有的走法的排列,平方級復雜度得到了所有走法的組合,線性級復雜度得到了所有小於等於n階台階的走法次數,而常數級只有n梯台階的走法次數。最后答案是10562230626642,有興趣的可以自己寫一下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM