今天下午去參加了大眾點評網的筆試,先是各種類似於公務員考試的語言推理、數字推理、圖形推理的題,我發現我在這方面真是弱爆了,到后面都沒時間做了,圖形推理題各種詭異。。。最后給了半個小時做兩道算法題,題目如下:
題目一:一個單入口單出口的有向無環圖中,要求在某些地方插入一些節點使得任何一條由起點到終點所經歷的節點數相同,類似於下面的圖,要求給出算法描述並分析時間復雜度。

如上圖所示,節點A到C有兩條路徑,ABC這條路徑經過了一個節點,而AC路徑經過了0個節點,我們的算法所要做的事就是要在AC路徑中間加入一個節點,然后ABC路徑和ADC路徑都經過了一個節點。
我在試卷上給出的解決方案:
(1)、首先求出最長路徑的長度n(意即最長的路徑經過n-1個節點),這個可以用DFS來做,應該比較容易
(2)、從出口節點開始,遍歷每個指向出口節點的節點,如果該節點已經是起始節點,則在這兩個節點之間插入n-1個節點,否則遞歸調用該過程,只不過傳遞給給節 點的路徑長度減1
那么這個解決方案是正確的么?考慮右面這個圖(請先忽略圖的紅色部分),按照我的算法,求得的n應該是3,然后遞歸到節點4時,發現節點1已經是起始節點,然后就在節點4和節點1之間插入一個新的節點8,然后問題出現了:最長路徑已經變成了4!所以這個算法是錯誤的,緊接着我又想到了,遞歸的過程可不可以從起始節點開
始?需要增加的節點插入到整個路徑的最后兩個節點之間,對於剛才的圖,這樣貌似是解決了問題,但是我們給剛才的圖加上紅色部分節點,我們用這個算法時,首先得到最長路徑為4,然后按1478路徑的長度是3,按照算法,我們將在節點7和節點8之間增加一個節點,剛才的問題又出現了,任何一條經過節點78的原來路徑長度為4的路徑,現在長度變為了5,還是出現了剛才的問題,所以這個算法也是錯誤的。總結一下,我們可以發現,剛才兩種算法錯誤的原因都是因為添加節點的位置是不對的,如果我們在正確的位置添加了節點(相對右圖就是在節點4和節點7指點添加一個節點,在節點4和8之間添加兩個節點),那么我們怎么做到這一點呢?我想到的一個算法是這樣的:對於每個節點維護一組信息,包括節點的層數(起始節點到該節點的路徑長度,起始節點設為0)以及生成該長度的父節點,相對於右圖,節點6維護的不經處理的信息就是:層數2來自節點3和節點4;節點7維護的不經處理的信息就是:層數3來自節點5和節點6以及層數2來自節點4;節點8的不經處理的信息是:層數4來自節點7,層數3來自節點7,層數2來自節點4。我們算法所要做的事就是最終使每個節點需要維護的層信息變為一個,即無論從那條路徑到該節點,該節點所處的層數都是固定的。算法如下:
1、初始化起始節點的層數信息
2、從起始節點開始遍歷每條路徑,遇到每個節點生成一個維護信息
(1)如果此節點不存在維護信息,創建之;
(2)如果該節點存在維護信息,有兩種情況:
(a)如果生成的維護信息的層數和原來已有的維護信息的層數是相同的,則合並這兩個維護信息,比如對於例子中的圖,節點5原來的維護信息為“層數3來自節點2”,然后從節點3到節點5生成的維護信息為“層數3來自節點3”,由於層數相同,我們可以將其合並為“層數3來自節點2和節點3”;
(b)如果生成的維護信息的層數和原來節點的維護信息的層數不一致,我們需要比較那一個的層數較大:
a.如果原來維護信息的層數較大,此時,我們只需要在生成此維護信息的節點與此節點之間插入一個新的節點,然后生成新節點的維護信息,然后從新節點開始(2)過程
b.如果新生成的維護信息的層數較大,將新生成的節點信息存入此節點,然后我們需要在生成原來維護信息的所有節點和此節點之間插入新節點,並且需要從所有的新插入節點開始(2)過程
整個算法就是這樣,需要遍歷的路徑(或者節點)我們用棧來存儲就可以~
當然這只是我暫時想出來的一種解決方案,或許里面存在錯誤,也肯定存在較為高效的算法,歡迎大家指正和指教~
題目二:論壇管理員管理論壇時,需要揪出論壇里的灌水者,灌水定義是這樣的:某段時間內總的發帖數是N,如果有超過N/2的貼是由某一個ID發的,那么這個ID就是灌水的。設計一個算法在最短的時間內找出該ID。
其實這個就是尋找眾數的問題,首先能想到的就是抽出所有帖子的ID,然后進行排序,取排序結果的最中間的ID即可,因為如果存在灌水者的話,他的ID的數目是大於N/2的,當然也有可能會出現誤殺,我們在得到這個ID后,再遍歷一遍所有ID,得到該ID的數目即可。這種方法的時間復雜度就是各種排序算法的時間復雜度。
由於規定N/2這個數目,我們可以有更簡單的算法:在所有ID中,我們每次不放回的取出兩個不同的ID,一直取到不能繼續取的時候,也就是說最后剩下的ID都是相同的,如果灌水者存在的話,那么這個ID肯定是灌水者的ID,當然還是存在誤殺的可能,我們可以繼續通過遍歷一遍來確定;這個方法可以推廣到N/A的發帖數目,我們每次只需要取A個不同的ID即可,A大於2的時候可能存在一些細節問題,都比較簡單了。
按照這種思路,在設定為N/2時我們有一個線性時間的算法,該算法的偽代碼如下,begin和end分別指向存儲ID的數組的起始位置和結束位置:
1 string find_most_appear(string *begin, string *end){ 2 string str = *begin, appear_times = 1; 3 ++begin; 4 while(begin != end) 5 { 6 if(*begin == str) 7 { 8 ++appear_times; 9 }else if(appear_times != 0){ 10 --appear_times; 11 }else{ 12 str = *begin; 13 appear_times = 1; 14 } 15 ++begin; 16 }
當然,還是有可能不存在灌水者,所以依然要進行驗證,時間復雜度O(n),沒什么說的。
半個小時做兩道還是比較費力的,這一題想到這的時候,已經到時間了,哈~
本文來自大笤帚,掃除一切障礙奮勇向前的大笤帚!
謝謝閱讀,並留下中肯意見。
