有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,咱们也只须要把题目给出的条件做为参数给到程序中,就能够了。
关于这个程序的执行效率问题,实际上程序就是靠遍历找到可能的排名状况,所以效率并不会过高。