给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] = [Ai, Bi]
和 values[i] 共同表示等式 Ai / Bi = values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。另有一些以数组 queries 表示的问题,其中 queries[j] = [Cj, Dj] 表示第 j 个问题,请你根据已知条件找出 Cj
/ Dj = ? 的结果作为答案。返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。
注意:输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。
示例 1:
输入:equations = [[“a”,“b”],[“b”,“c”]], values = [2.0,3.0], queries =
[[“a”,“c”],[“b”,“a”],[“a”,“e”],[“a”,“a”],[“x”,“x”]]
输出:[6.00000,0.50000,-1.00000,1.00000,-1.00000] 解释: 条件:a / b = 2.0, b /
c = 3.0 问题:a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
结果:[6.0, 0.5, -1.0, 1.0, -1.0 ]示例 2:
输入:equations = [[“a”,“b”],[“b”,“c”],[“bc”,“cd”]], values =
[1.5,2.5,5.0], queries = [[“a”,“c”],[“c”,“b”],[“bc”,“cd”],[“cd”,“bc”]]
输出:[3.75000,0.40000,5.00000,0.20000]示例 3:
输入:equations = [[“a”,“b”]], values = [0.5], queries =
[[“a”,“b”],[“b”,“a”],[“a”,“c”],[“x”,“y”]]
输出:[0.50000,2.00000,-1.00000,-1.00000]提示:
1 <= equations.length <= 20 equations[i].length == 2 1 <= Ai.length, Bi.length <= 5 values.length == equations.length 0.0 < values[i] <= 20.0 1 <= queries.length <= 20 queries[i].length == 2 1 <= Cj.length, Dj.length <= 5 Ai, Bi, Cj, Dj 由小写英文字母与数字组成
先构造图,使用dict实现,其天然的hash可以在in判断时做到O(1)复杂度。
对每个equation如"a/b=v"构造a到b的带权v的有向边和b到a的带权1/v的有向边,
之后对每个query,只需要进行dfs并将路径上的边权重叠乘就是结果了,如果路径不可达则结果为-1。
具体实现见代码
什么是并查集以及带权并查集?
合并过程是将已知条件转换为森林,通过字典实现每个节点关系的连接,并在不断合并的过程中,通过查找不断更新当前合并中有关节点与根节点的权值关系;
查找过程也是更新过程,更新有关节点与根节点的权值。
如a/b = 4,那么在合并中节点a的权值为4,节点b的权值为1。
代码比较长,可以以简单的例子debug一下,跟着走,过程虽然比较绕,但原理并不复杂
例如a/b=2,b/c=2,a/d=2,求c/d=?
其实可以发现,构图+DFS和带权并查集思路是一样的,只是构图+DFS没有合并压缩的操作使每个节点都与根节点挂钩,但使用某个节点时,直接找其根节点即可,而是采用了遍历搜索的形式,而合并和压缩的操作缩短了查询时间,优化整体结构,这就是带权并查集的优势。
def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
# 构造图,equations的第一项除以第二项等于value里的对应值,第二项除以第一项等于其倒数
graph = {
}
for (x, y), v in zip(equations, values):
if x in graph:
graph[x][y] = v
else:
graph[x] = {
y: v}
if y in graph:
graph[y][x] = 1/v
else:
graph[y] = {
x: 1/v}
# dfs找寻从s到t的路径并返回结果叠乘后的边权重即结果
def dfs(s, t) -> int:
if s not in graph:
return -1
if t == s:
return 1
for node in graph[s].keys():
if node == t:
return graph[s][node]
elif node not in visited:
visited.add(node) # 添加到已访问避免重复遍历
v = dfs(node, t)
if v != -1:
return graph[s][node]*v
return -1
# 逐个计算query的值
res = []
for qs, qt in queries:
visited = set()
res.append(dfs(qs, qt))
return res
from collections import namedtuple, defaultdict
class Solution:
def calcEquation(self, equations, values, queries) :
class Item:
def __init__(self, parent, value):
self.parent = parent
self.value = value
father = defaultdict(Item)
def find(x):
#返回值为根节点
#如果它是独立元素,根节点是自身,返回自己。不然,递归找到根节点,用路径压缩修改parent,并返回根节点。
if x != father[x].parent:
t = father[x].parent
father[x].parent = find(t)
father[x].value *= father[t].value #1---3的权重就是(1--2的权重)*(2---3的权重)
return father[x].parent
return x
def union(e1, e2, result):
# 平行四边形法则边 (e1, e2), (e1, f1), (e2, f2) --> (f1, f2)
#找到根节点判断是否相等
f1 = find(e1)
f2 = find(e2)
#分别属于两个集合,需要合并:
if f1 != f2:
father[f1].parent = f2 #第一个根节点指向第二个根节点
#更新权重,具体方法通过上面的例子可以算一下
father[f1].value = father[e2].value * result / father[e1].value
#用字典提高效率,判断是否见过
number = defaultdict(int)
#初始化:每个节点的父节点初始化为自身,不指向任何人,value为1
for i,item in enumerate(equations) :
s1, s2 = item[0], item[1]
if s1 not in number:
number[s1] = 1
father[s1] = Item(parent=s1, value=1)
if s2 not in number:
number[s2] = 1
father[s2] = Item(parent=s2, value=1)
#根据两个节点的关系,合并两个节点。构建tree
union(s1, s2, values[i])
#开始计算:
ans = []
for s1, s2 in queries:
#从来没见过,直接-1
if s1 not in number or s2 not in number:
ans.append(-1.0)
continue
#找到根节点,如果不想等,说明在不同集合tree中,没有任何关系,直接-1
e1, e2 = s1, s2
f1, f2 = find(e1), find(e2)
if f1 != f2:
ans.append(-1.0)
else:
#在同一个tree中
v1 = father[e1].value
v2 = father[e2].value
#v1=e1/root v2=e2/root 则e1/e2=v1/v2
ans.append(v1 / v2)
return ans