1. 程式人生 > >8分鐘寫出程式碼的華為面試題?不要被標題迷惑!

8分鐘寫出程式碼的華為面試題?不要被標題迷惑!

在程式設計師的江湖中,有一個流傳了很久的故事,那就是:
華為面試題(8分鐘寫出程式碼):
有兩個陣列a,b,大小都為n,陣列元素的值任意,無序;
要求:通過交換a,b中的元素,使陣列a元素的和與陣列b元素的和之間的差最小
華為面試題(8分鐘寫出程式碼) (注意發帖時間:2006年)

可憐一個“8分鐘”,讓無數程式設計師在8小時、8天、8周、甚至8年仍然未找出滿意的答案而掉入了自卑的漩渦無法自拔!

這道題根本沒有完美的解決方案。因為嚴格而言他是一個NP-Hard問題!
其本質屬於劃分問題:Partition problem

交換方法的侷限性

所有解決思路中,用排序、交換來解的,無一例外都會陷入區域性最優。
對於交換演算法,有朋友給出了一系列分析和證明:

交換兩個陣列使兩個陣列和的差最小
然而,這種方法只能保證每一次交換比上一次更優,就和梯度下降演算法一樣,只能保證最終達到一個區域性最優值。
為何是區域性最優而不是全域性最優?考慮用上述交換演算法解決這個例子:
a =[ 4 7 7 13]
b =[ 5 5 9 10]
此時,sum (a) = 31 > sum(b) = 29。

因此從a中選擇一個比b大的數交換,且保證兩陣列和的差距不會更大。
(注:如果必須滿足差距更小才交換,則演算法直接停止)
唯一的選擇是交換7和5,得到:
a =[ 4 5 7 13]
b =[ 4 7 9 10]
此時,sum (a) = 29 < sum(b) = 31。

是否感受到崩潰?這就是所謂的區域性最優。你永遠無法使a和b相等。
實際上,這個例子的最優解需要同時交換a中的4,7與b中的5,5:
a =[ 5 5 7 13]
b = [4 7 9 10]
此時sum (a) = 30 == sum(b) = 30。

可見,單一的交換a,b中的元素,永遠達不到這個狀態。
有人說那演算法改成同時交換2個呢?
治標不治本。那如果遇到需要同時交換x個的初始序列,你咋辦?

簡化情況下的全域性最優解

當然,在極度簡化的情況下,是可以使用動態規劃(Dynamic Program)得到完美解的。
所謂極度簡化,是要求n很小,而陣列中的每個數字也很小的情況。
下面我以n=1000,數字中每個數字最大不超過k=1000的正整數做說明。
這時候,就是加了一個物品數量限制的揹包問題。即給定1000個數字,選其中500個來使得使得大小為(sum(a)+sum(b))/2的揹包儘可能滿。
極端情況下,1000個數字加起來大小為n*k=100萬。因此揹包問題的空間輔助度為O(n*k)。
由於存在物品數量限制,需要一個額外的空間維度記錄揹包中物品數量(n),因此需要開闢一個二維陣列儲存子問題解,總的空間複雜度為O(n*n*k)。
所以需要記憶體為1000*1000*1000 = 1G。
當然,嚴格來說,揹包大小可以取n*k的一半。揹包中物品數量也可取n的一半。這種常數就不討論了。

重點是n,k太大,你揹包直接撐爆。如陣列數字為整數(k=2^32)
就算你機器nb,撐不爆記憶體,O(n*n*k)的時間複雜度也能讓你爆炸。

總結

總之,這道題陣列大小沒指定,即n未知;也沒有說陣列中的數的範圍,k也未知。
如果你在面試中遇到這類問題,需要好好的分析資料範圍。
如果n,k足夠小,使用DP。
如果n,k太大,難以直接得出精確解。
這個時候,你應該考慮如何求得近似解。上述交換演算法是一種思路。但是該演算法必然陷入區域性最優解。
你也可以擴充套件使用人工智慧領域的一些隨機優化方法,如模擬退火,蟻群演算法,遺傳演算法等等。這類方法具有一定的跳出區域性最優解的能力。
相關的例子,可參考我之前的一篇博文:
模擬退火演算法解旅行商(TSP)問題
又到程式設計師招聘季節!應試的程式設計師童鞋一定要引以為戒!
面試是靈活的,題目也是靈活的。
遇到問題,不一定要一開始就思考如何動筆,而是多思考,多和麵試官交流。
面試不以給出答案為唯一目標,重要的是是否展現清晰的思路和冷靜的解決問題能力

最後,至於這題DP的程式碼,微軟技術面試心得——《程式之美》中有類似的題。
該數章節2.18《陣列分割》例子中,給出了DP的虛擬碼。此處不再贅述。