有A、B、C、D、E 5所學校,在一次檢查評比中,已知E校確定不是第2名或第3.他們相互進行推測。
A校有人說,E校必定是第1名。
B校有人說,我校多是第2名。
C校有人說,A校最差。
D校有人說,C校不是最好的。
E校有人說,D校會得到第1名。
結果只有第1名和第2名學校的人猜對了,編程指出這5所學校的名次。python
每一個人的猜想結果均可能對,但只有兩我的猜對了。能夠直接取猜對的兩我的的猜想結果,以及另外三個猜錯的結果的反面,與已知條件進行對比,若不存在矛盾選項,且每一個學校的排名可以惟一肯定,認爲選取的猜對的人是正確的。git
例如,已知有 A、B、C、D、E 5 所學校及其相應的猜想結果,咱們能夠獲得相似這樣的數據結構:github
schools = ('A', 'B', 'C', 'D', 'E') pred = [ { "Pos": ('E', (1, ), "D"), "Neg": ('E', (4, 5)) # E 不會是 2 3 }, # ... ]
pred 是一個數組,數組中有 5 個元素,表示 5 個學校的預測結果,每一個元素(預測結果)是一個字典。用 「Pos」 表示該學校猜對的結果,「Neg」 表示相反的結果。編程
將「A校有人說,E校必定是第1名。」 轉化爲數據結構就是 { "Pos": ('E', (1, ), "D") }
,Pos 元祖第 1 個元素 「E」 表示對 E 校的猜想,第 2 個元素 (1, ) 表示猜想的排名,爲了統一,第 2 個用一個 tuple 表示,第 3 個元素 「D」 表示 A 的猜想是確定
的。而 B 校的推測是 「可能」,所以用 「M」 表示。數組
題目中還給了一個條件,用數組表示:數據結構
# 猜對的人的學校排名是 1 或 2 RankFromRightPeople = [1, 2] # 猜錯的人的學校排名是 3 4 5 RankFromWrongPeople = [3, 4, 5]
當對某個學校的排名進行可能性分析時,其實是利用已知的條件,對不一樣人給出的猜想或已知條件中的可能性作一個交集的操做。函數
而後咱們開始嘗試遍歷全部可能的正確的學校,獲得惟一的且不矛盾的結果便可。code
咱們對學校的排名預測中會用到交集和差集,這裏利用 python set 的 API 進行交集或差集運算:blog
def union(A, B) : """ A + B """ return list(set(A).intersection(B)) def diff(A, B): """ A - B """ return list(set(A).difference(set(B)))
接下來,對某種可能的排名狀況進行分析。
根據給定的條件,咱們能夠遍歷前兩名的學校的可能狀況,因爲總共有 5 所學校,因此可能的狀況有 10 種。leetcode
每次遍歷的時候,從 pred 數組中取出各個學校對於排名的描述,將排名的描述(python 中的數組來表示)放入 predictSchool 數組中:
predictSchool = [0] * len(schools) # 收集結果集 for index, p in enumerate(pred): if si == index or sj == index: predictSchool[schools.index(p["Pos"][0])] = list(p["Pos"][1]) else: predictSchool[schools.index(p["Neg"][0])] = list(p["Neg"][1])
假設 A 和 B 學校在前兩名,那麼對 A 和 B 的描述中就要加上交集 (1, 2),另外 3 所學校的描述中要加上交集 (3, 4, 5):
# 取交集 union for i in range(len(schools)): if i == si or i == sj: predictSchool[i] = union(predictSchool[i], RankFromRightPeople) else: predictSchool[i] = union(predictSchool[i], RankFromWrongPeople)
此時 predictSchool 應該是一個二維數組,它的數組相似於:
[ [1, 2, 3], [1, 2, 5], [3, 4], [3, 5], [3, 4, 5] ]
接下來,對每一個學校的排名描述作差集處理,當 predictSchool 中出現某一項的元素爲 0,說明描述出現矛盾,咱們假設的第 1 名、第 2 名是錯誤的,能夠直接中止分析:
for _ in range(len(schools) * 2): # 只會執行必定次數循環 s = set() # print("Before", predictSchool) for pr in predictSchool: lpr = len(pr) if lpr == 0: return [] elif lpr == 1: if pr[0] in s: # print("Already definite.") return [] s.add(pr[0]) # print("Set 2 list", s) for i, pr in enumerate(predictSchool): lpr = len(pr) if lpr > 1: predictSchool[i] = diff(pr, s)
當 predictSchool 中出現某一項的元素數目爲 1 時,代表這個學校的排名當前能夠肯定下來,接下來將 prediectSchool 中其餘項的數組中去除掉這個排名的描述,最後循環到 predictSchool 中的每一項都只有一個元素時,說明咱們猜想的第 1 名和第 2 名學校是可能的。
接下來只須要遍歷全部可能的 1 2 名的狀況便可:
def startPredict(): for i in range(5): for j in range(i+1, 5): pr = predictRank(i, j) if len(pr) > 0: for k, p in enumerate(pr): pr[k] = [schools[k], p[0]] dpr = dict(pr) npr = sorted(dpr.items(), key = lambda item: item[1]) print(npr) # 這裏其實能夠中止循環了 # return # print(pr, i, j, '\n---------') startPredict()
運行咱們的 Python 程序,能夠獲得以下結果:
[('C', 1), ('B', 2), ('D', 3), ('E', 4), ('A', 5)]
完整的 python 程序能夠從 個人 github 倉庫 中獲取。
至此爲止,這個 Python 程序已經能夠找到正確的排名了,但它還有改進的空間。還記得咱們定義 pred 數組的時候是怎麼樣的嗎:
schools = ('A', 'B', 'C', 'D', 'E') pred = [ { "Pos": ('E', (1, ), "D"), "Neg": ('E', (4, 5)) # E 不會是 2 3 }, # ... ]
pred 數組中的 "Pos" 項就是題目所描述的,然而 pred 數組中的 "Neg" 項是咱們從程序中獲取並通過了處理的,可是實際上它並不須要人爲的處理,咱們能夠寫一個簡單的函數對 pred 的 "Pos" 項作一個預處理,將其轉化爲 "Neg" 項。
須要注意的是,題目有個前置條件,E 學校不是第 2 名,也不是第 3 名。預處理的過程若是有對 E 學校的描述時,要與這個條件求交集
我已經給它留好了擴展的空間,"Pos" 對應 value 的第 3 個元素表示猜想的可能性,咱們能夠根據第 3 個元素和第 2 個元素推導出 "Neg":
加上這樣的預處理後,實際上解決這類問題就不用修改太多代碼,並且也不須要人爲的計算出每一個學校的 Pos 項和 Neg 項了。就是說這個程序的擴展性仍是比較好的。
若是學校變多了,好比從 A 到 G,咱們也只須要把題目給出的條件做爲參數給到程序中,就能夠了。
關於這個程序的執行效率問題,實際上程序就是靠遍歷找到可能的排名狀況,所以效率並不會過高。