最近2個月時間都比較忙,另外還有些其餘的事情,幾乎沒有怎麼作題和寫文章了,懼怕本身又開始懶散起來了,因此仍是督促本身不斷地學習和練習編碼。最近還須要好好學下python面向對象的一些知識了。
今天咱們來分析一個JD的2016實習生招聘題目,該題目在賽碼網上標爲5星難度,我認爲這個題目的難點在於對題目的理解,而且如何在較短的時間內選擇一個更佳的算法來完成coding。python
<題目來源: 京東2016實習生招聘 原題連接-可在線提交(賽碼網)>算法
BF的生日快到了,這一次,小東決定爲BF送一份特別的生日禮物爲其慶生。做爲高智商中的佼佼者,BF在國外求學,所以小東沒法與之一塊兒慶生。小東計劃送一個生日卡片,並經過特別的包裝讓BF永遠難忘。app
她決定把卡片套裝在一系列的信封A = {a1, a2, ..., an}中。小東已經從商店中購買了不少的信封,她但願可以用手頭中儘量多的信封包裝卡片。爲防止卡片或信封被損壞,只有長寬較小的信封可以裝入大些的信封,同尺寸的信封不能套裝,卡片和信封都不能摺疊。學習
小東計算了郵寄的時間,發現她的時間已經不夠了,爲此找你幫忙包裝,你能幫她嗎?優化
首先根據樣例確認題目要求。
這裏我再給出一組樣例數據而且給出正確輸出來驗證是否完全理解題目要求:
5 1000 998
5002 5005
5003 5004
5003 5002
5002 5001
5002 5002
正確的輸出是
2
4
2編碼
注意到如下3個問題:
(1)題目要求的卡片必須是裝入能裝入且最小的一個信封
(2)若是有多個信封能夠選擇,那麼選擇先拿到手上的,也就是在樣例中先出現的封信,具體能夠參見我給出的那組樣例輸出,最後選擇了2而不是3,就是覺得在2,3均可用時,選擇先出現的,也就是第2個封信
(3)信封不能旋轉,可是這點彷佛題目中並無說起到spa
如今咱們來討論如何解決這個問題
因爲信封要被一層一層的封裝,而且要用到儘量多的信封。獲得的解若是是r[1], r[2], ... r[n],那麼知足r[i] < r[i + 1] ('<'表示左側能夠被右側裝下),這有什麼用呢?這提示咱們須要對其進行一個整理,獲得一個整理後的序列知足:
envelop[1], envelop[2], ... envelop[i], ... envelop[n]
envelop[j] !> envelop[i] (i > j) 其中('!>' 表示左側能不右側裝下信封,>表示左側能夠裝下右側信封)
這樣的話,咱們能夠考慮使用面積進行排序。所以面積大的不必定的下面積小的信封,可是面積小的必定裝不下面積大的信封 。.net
在獲得這樣的一個具體拓撲關係(關於拓撲排序/結構,能夠參考相關資料)序列以後,若是你對動態規劃(DP)有所瞭解,彷佛首先會想到最長不降低子序列(LIS)問題,由於在獲得拓撲序列後,咱們能夠按這個序列來劃分階段,來進行動態規劃。code
設f[i]表示,若是選擇i個信封來裝能夠最多使用的信封個數,同時設g[i]記錄i的前一個使用的信封的下標。因爲最後咱們要輸出信封的順序編號,別忘記了帶上原來的順序編號,在拓撲排序的時候,咱們能夠在遇到多個入度爲0的點時,優先選擇編號較小的點加入拓撲序列,方便後續DP時的處理。
f[i] = max(f[j]) + 1, i > j and f[j] > 0 and envelop[i] > envelop[j]
g[i] = j (f[J] = f[i], j = min{J})
特別注意的是,因爲有多個信封可用時須要選擇編號較小的,計算g[i]時,當有多個f[j]都知足條件時,選擇最小的那個j。
最後的答案是max(f[i]),最後一個選擇是信封是i最小的那個。而後咱們根據g[i]能夠倒推出全部選擇信封。對象
至此,問題獲得瞭解決。
可是,咱們再諮詢觀察獲得的拓撲序列發現,當肯定最開始的信封以後,若是envelop[i] < envelop[j],而且j是第一個知足這個條件的信封,顯然咱們會選擇j,由於後面的信封只存在兩種狀況,不能裝下j,可是都比較j晚出現,不會選擇。若是能把j裝下,且第一個出現,咱們依然會選擇。反之,若是不按這個策略選擇,顯然不可能獲得更好的解。
簡單反證以下(若是用數學概括法也相似):
如今有序列envelop[] 其中envelop[0]是最小能裝入卡片的信封,envelop[i]是第一個能夠裝下envelop[0]的信封,假設選擇envelopj獲得更優的解,那麼存在兩種狀況:
1) envelop[j] > envelop[i],這時加入envelop[i]顯然可使用多一個信封,與假設矛盾
2) envelop[j] !> envelop[j],選擇更早出現的i才符合題目要求,與假設矛盾
一些後話:
1.這個題目不必定每一個人都會馬上想到使用相似LIS的算法,好比不會LIS,又好比一眼就能看出問題本質,使用更佳的解法。更多時候咱們多是一步一步進行算法的演進,而後再不斷的進行更好的選擇和優化。問題能夠有多種正確解法,可是並非每種正確解法都是最優的。
2.關於拓撲排序的方式有多種,我這裏並無使用基於圖的方法,由於須要建圖(O(n^2))在時間上相對我直接使用O(n^2)的插入排序的方法並無優點。
具體實現的時候,我每次在原來的信封序列中取一個,放在已經獲得的拓撲序列封信序列的第一個位置,而後和後一個比較,看是否須要交換,若是交換後,繼續向後比較,不須要交換則比較結束,繼續這個過程知道原來的信封已經取完
envelop a和envelop b交換的條件是:
envelop a > envelop b
or
envelop a !> envelop b and envelop a !< envelop b and Pa > Pb
其中Pa > Pb表示a在原給出的信封序列中位置更靠後
3.最後,在參考其餘答案的時候,我也發現了一個O(nlogn)的算法,算法思想和咱們提到的大體相同,他首選是按面積進行一個排序,而後僅經過O(n)的一次掃描就獲得最後的答案,思想比較巧妙。有興趣能夠在題目連接的「正確答案」中查看。
4.此外,關於LIS算法以及優化能夠參考如下文章:
http://blog.csdn.net/hulifang...
http://blog.csdn.net/wall_f/a...
import sys const_w = 0 const_h = 1 const_p = 2 def is_swap(a, b): a_contain_b = True if a[const_w] > b[const_w] and a[const_h] > b[const_h] else False b_contain_a = True if a[const_w] < b[const_w] and a[const_h] < b[const_h] else False if a_contain_b or (not a_contain_b and not b_contain_a and a[const_p] > b[const_p]): return True return False def sort(envelops): s_envelops = [] for envelop in envelops: s_envelops.insert(0, envelop) for i in range(len(s_envelops) - 1): if is_swap(s_envelops[i], s_envelops[i + 1]): temp = s_envelops[i] s_envelops[i] = s_envelops[i + 1] s_envelops[i + 1] = temp else: break return s_envelops def main(): envelops = [] while True: line = map(int, sys.stdin.readline().strip().split()) if len(line) < 3: return n, w, h = line[0], line[1], line[2] for i in range(n): line = map(int, sys.stdin.readline().strip().split()) envelops.append((line[0], line[1], i + 1)) s_envelops = sort(envelops) card_in = -1 min_area = sys.maxint for i, s_envelop in enumerate(s_envelops): if s_envelop[const_w] > w and s_envelop[const_h] > h and s_envelop[const_w] * s_envelop[const_h] < min_area: min_area = s_envelop[const_w] * s_envelop[const_h] card_in = i if card_in < 0: print 0 else: cnt = 1 results = [s_envelops[card_in][const_p]] last = s_envelops[card_in] for i in range(card_in + 1, n): if s_envelops[i][const_w] > last[const_w] and s_envelops[i][const_h] > last[const_h]: cnt += 1 results.append(s_envelops[i][const_p]) last = s_envelops[i] print cnt for result in results: print result, print envelops[:] = [] # for s_envelop in s_envelops: # print s_envelop[const_w], s_envelop[const_h], s_envelop[const_p] if __name__ == '__main__': main()