每年十一月各大it公司都不約而同、爭后恐後地到各大高校進行全國巡迴招聘。與此同時,網上也開始出現大量筆試面試題;網上流傳的題目往往都很精巧,既能讓考查基礎知識,又在平淡中隱含了廣闊的天地供優秀學生馳騁。
這兩天在網上淘到一道筆試題目(注1),雖然真假未知,但的確是道好題,題目如下:
從10億個浮點數中找出最大的1萬個。
這是一道似易實難的題目,一般同學最容易中的陷阱就是沒有重視這個「億」字。因為有10億個單精度浮點數元素的陣列在32位平台上已經達到3.7gb之巨,在常見計算機平台(如win32)上宣告乙個這樣的陣列將導致堆疊溢位。正確的解決方法是分治法,比如每次處理100萬個數,然後再綜合起來。不過這不是本文要討論的主旨,所以本文把上題的10億改為1億,把浮點數改為整數,這樣可以直接地完成這個問題,有利於清晰地討論相關演算法的優化(注2)。
不假思索
拿到這道題,馬上就會想到的方法是建立乙個陣列把1億個數裝起來,然後用for迴圈遍歷這個陣列,找出最大的1萬個數來。原因很簡單,因為如果要找出最大的那個數,就是這樣解決的;而找最大的1萬個數,只是重複1萬遍而已。
template< class t >
void solution_1( t bigarr, t resarr )
resarr[i] = bigarr[idx];
std::swap( bigarr[idx], bigarr[i] );}}
設big_arr_size = 1億,res_arr_size = 1萬,執行以上演算法已經超過40分鐘(注3),遠遠超過我們的可接受範圍。
稍作思考
從上面的**可以看出跟selectsort演算法的核心**是一樣的。因為selectsort是乙個o(n^2)的演算法(solution_1的時間複雜度為o(n*m),因為solution_1沒有將整個大陣列全部排序),而我們又知道排序演算法可以優化到o(nlogn),那們是否可以從這方面入手使用更快的排序演算法如mergesor、quicksort呢?但這些演算法都不具備從大至小選擇最大的n個數的功能,因此只有將1億個數按從大到小用quicksort排序,然後提取最前面的1萬個。
template< class t, class i >
void solution_2( t bigarr, t resarr )
因為stl裡的sort演算法使用的是quicksort,在這裡直接拿來用了,是因為不想寫乙個寫乙個眾人皆知的quicksort**來佔篇幅(而且stl的sort高度優化、速度快)。
對solution_2進行測試,執行時間是32秒,約為solution_1的1.5%的時間,已經取得了幾何數量級的進展。
深入思考
壓抑住興奮回頭再仔細看看solution_2,你將發現乙個大問題,那就是在solution_2裡所有的元素都排序了!而事實上只需找出最大的1萬個即可,我們不是做了很多無用功嗎?應該怎麼樣來消除這些無用功?
如果你一時沒有頭緒,那就讓我慢慢引導你。首先,發掘乙個事實:如果這個大陣列本身已經按從大到小有序,那麼陣列的前1萬個元素就是結果;然後,可以假設這個大陣列已經從大到小有序,並將前1萬個元素放到結果陣列;再次,事實上這結果陣列裡放的未必是最大的一萬個,因此需要將前1萬個數字後續的元素跟結果陣列的最小的元素比較,如果所有後續的元素都比結果陣列的最小元素還小,那結果陣列就是想要的結果,如果某一後續的元素比結果陣列的最小元素大,那就用它替換結果陣列裡最小的數字;最後,遍歷完大陣列,得到的結果陣列就是想要的結果了。
template< class t >
void solution_3( t bigarr, t resarr )
}//這個後續元素比resarr中最小的元素大,則替換。
if( bigarr[i] > resarr[idx] )
else
bexchanged = false;}}
上面的**使用了乙個布林變數bexchanged標記是否發生過交換,這是乙個前文沒有談到的優化手段——用以標記元素交換的狀態,可以大大減少查詢resarr中最小元素的次數。也對solution_3進行測試一下,結果用時2.0秒左右(不使用bexchanged則高達32分鐘),遠小於solution_2的用時。
深思熟慮
在進入下一步優化之前,分析一下solution_3的成功之處。第一、solution_3的演算法只遍歷大陣列一次,即它是乙個o(n)的演算法,而solution_1是o(n*m)的演算法,solution_2是o(nlogn)的演算法,可見它在本質上有著天然的優越性;第
二、在solution_3中引入了bexchanged這一標誌變數,從測試資料可見引入bexchanged減少了約99.99%的時間,這是乙個非常大的成功。
上面這段話絕非僅僅說明了solution_3的優點,更重要的是把solution_3的主要矛盾擺上了桌面——為什麼乙個o(n)的演算法效率會跟o(n*m)的演算法差不多(不使用bexchanged)?為什麼使用了bexchanged能夠減少99.99%的時間?帶著這兩個問題再次審視solution_3的**,發現bexchanged的引入實際上減少了如下**段的執行次數:
for( idx = 0, j = 1; j < res_arr_size; ++j )
上面的**段即是查詢resarr中最小元素的演算法,分析它可知這是乙個o(n)的演算法,到此時就水落石出了!原來雖然solution_3是乙個o(n)的演算法,但因為內部使用的查詢最小元素的演算法也是o(n)的演算法,所以就退化為o(n*m)的演算法了。難怪不使用bexchanged使用的時間跟solution_1差不多;這也從反面證明了solution_3被上面的這一**段導致效能退化。使用了bexchanged之後因為減少了很多查詢最小元素的**段執行,所以能夠節省99.99%的時間!
面試遇到的一道演算法題
今天面試摩拜單車遇到一道演算法題,題目如下 給定乙個正整數範圍 m,n 返回乙個陣列,該陣列是每個正整數轉換為相應二進位制之後包含1的個數 示例 給定 1,5 1的二進位制位1,二進位制表示中包含位1的個數字1,2的二進位制位10,二進位制表示中包含位1的個數字1,3的二進位制為11,二進位制表示中...
一道關於排序的演算法題
題目 給定乙個無序陣列a,一直這個陣列a中的任意乙個元素所在的位置離其最終排序後的位置相差的距離不會超過k。即排序前a i 在位置i,那麼在排序後該元素的位置會在 i k,i k 這個範圍內。現在要求給出乙個演算法使得演算法盡量高校。解析 這裡就不多說了,直接給自己的解法吧。利用乙個大小為k 1的最...
一道演算法題
兩個燒杯,乙個放糖乙個放鹽,用勺子舀一勺糖到鹽,攪拌均勻,然後舀一勺混合 物會放糖的燒杯,問你兩個燒杯哪個雜質多?一樣多吧 對的 為啥?是不是因為 糖和鹽本來就是均勻的 因為,就算不攪拌均,你放一勺過去,那邊放一勺不含雜質的過來,那麼都是一勺雜之 如果攪拌均勻的話也是一樣 小依 21 45 32 也...