You are given an array of variable pairs equations and an array of real numbers values, where equations[i] = [Ai, Bi] and values[i] represent the equation Ai / Bi = values[i]. Each Ai or Bi is a string that represents a single variable.
You are also given some queries, where queries[j] = [Cj, Dj] represents the jth query where you must find the answer for Cj / Dj = ?.
Return the answers to all queries. If a single answer cannot be determined, return -1.0.
Note: The input is always valid. You may assume that evaluating the queries will not result in division by zero and that there is no contradiction.
Note: The variables that do not occur in the list of equations are undefined, so the answer cannot be determined for them.
Example 1:
Input: equations = [["a","b"],["b","c"],["ab","bc"]], values = [4.0,1.0,3.25], queries = [["a","c"],["b","a"],["c","c"],["ab","a"],["d","d"]]
Output: [4.00000,0.25000,1.00000,-1.00000,-1.00000]Example 2:
Input: equations = [["a","b"]], values = [0.5], queries = [["a","b"],["b","a"]]
Output: [0.50000,2.00000]Constraints:
1 <= equations.length, queries.length <= 20equations[i].length == queries[i].length == 21 <= Ai.length, Bi.length, Cj.length, Dj.length <= 5values.length == equations.length0.0 < values[i] <= 20.0Ai, Bi, Cj, Dj consist of lower case English letters and digits.Before attempting this problem, you should be comfortable with:
We can think of each equation a / b = value as a directed edge from a to b with weight value, and an edge from b to a with weight 1/value. This transforms the problem into a graph traversal: to evaluate x / y, we need to find a path from x to y and multiply the edge weights along that path.
BFS works well here because we explore all neighbors at the current distance before moving further. Starting from the source variable, we track the accumulated product as we traverse. When we reach the target, that accumulated product gives us the answer.
(neighbor, weight) pairs. For each equation a / b = value, add edges a -> b with weight value and b -> a with weight 1/value.(src, target):-1.(src, 1.0) and a visited set.target, return the accumulated weight.target is never reached, return -1.class Solution:
def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
adj = collections.defaultdict(list) # Map a -> list of [b, a/b]
for i, eq in enumerate(equations):
a, b = eq
adj[a].append((b, values[i]))
adj[b].append((a, 1 / values[i]))
def bfs(src, target):
if src not in adj or target not in adj:
return -1
q, visit = deque([(src, 1)]), set()
visit.add(src)
while q:
node, w = q.popleft()
if node == target:
return w
for nei, weight in adj[node]:
if nei not in visit:
q.append((nei, w * weight))
visit.add(nei)
return -1
return [bfs(q[0], q[1]) for q in queries]Where is the number of unique strings and is the number of queries.
Like BFS, DFS also explores paths through the graph, but it goes deep before backtracking. We recursively explore from the source, multiplying edge weights as we go. The first complete path we find from source to target gives us the answer.
DFS is often simpler to implement recursively. At each step, we check if we have reached the target. If not, we try all unvisited neighbors, and if any recursive call succeeds (returns a non-negative result), we multiply by the current edge weight and return.
(src, target):-1.src == target, return 1.0 (any variable divided by itself is 1).src as visited and explore all neighbors recursively.target. If found, multiply the result by the edge weight and return.-1.class Solution:
def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
adj = collections.defaultdict(list) # Map a -> list of [b, a/b]
for i, eq in enumerate(equations):
a, b = eq
adj[a].append((b, values[i]))
adj[b].append((a, 1 / values[i]))
def dfs(src, target, visited):
if src not in adj or target not in adj:
return -1
if src == target:
return 1
visited.add(src)
for nei, weight in adj[src]:
if nei not in visited:
result = dfs(nei, target, visited)
if result != -1:
return weight * result
return -1
return [dfs(q[0], q[1], set()) for q in queries]Where is the number of unique strings and is the number of queries.
Union-Find can track connected components, but here we need to track ratios between variables. The key insight is to store each variable's weight relative to its root. When we union two variables x and y with ratio x/y = value, we connect their roots and adjust weights so that the ratio relationship is preserved.
To query x/y, we find both roots. If they differ, no path exists. If they match, the answer is weight[x] / weight[y], since both weights are relative to the same root.
parent and weight maps. Each variable starts as its own parent with weight 1.0.find operation uses path compression: while finding the root, update the weight to be relative to the root by multiplying along the path.union operation connects two variables: find both roots, and if different, set one root's parent to the other and adjust the weight to maintain the given ratio.union for each pair.(x, y):-1.weight[x] / weight[y].class UnionFind:
def __init__(self):
self.parent = {}
self.weight = {}
def add(self, x):
if x not in self.parent:
self.parent[x] = x
self.weight[x] = 1.0
def find(self, x):
if x != self.parent[x]:
orig_parent = self.parent[x]
self.parent[x] = self.find(self.parent[x])
self.weight[x] *= self.weight[orig_parent]
return self.parent[x]
def union(self, x, y, value):
self.add(x)
self.add(y)
root_x = self.find(x)
root_y = self.find(y)
if root_x != root_y:
self.parent[root_x] = root_y
self.weight[root_x] = value * self.weight[y] / self.weight[x]
def get_ratio(self, x, y):
if x not in self.parent or y not in self.parent or self.find(x) != self.find(y):
return -1.0
return self.weight[x] / self.weight[y]
class Solution:
def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
uf = UnionFind()
for (a, b), value in zip(equations, values):
uf.union(a, b, value)
return [uf.get_ratio(a, b) for a, b in queries]Where is the number of unique strings and is the number of queries.
Floyd-Warshall computes shortest paths between all pairs of nodes. Here, instead of distances, we compute the product of ratios. If we know a/b and b/c, then a/c = (a/b) * (b/c). We precompute all such transitive ratios.
This approach trades query time for preprocessing time. After running Floyd-Warshall, each query becomes a simple lookup.
graph[a][b] = value for equation a/b = value, and graph[b][a] = 1/value.k, and for each pair of nodes (i, j) connected through k, compute graph[i][j] = graph[i][k] * graph[k][j] if this path does not already exist.(a, b):a is in the graph and b is reachable from a, return graph[a][b].-1.class Solution:
def calcEquation(self, equations, values, queries):
graph = collections.defaultdict(dict)
for (a, b), value in zip(equations, values):
graph[a][b] = value
graph[b][a] = 1.0 / value
for k in graph:
for i in graph[k]:
for j in graph[k]:
if j not in graph[i]:
graph[i][j] = graph[i][k] * graph[k][j]
result = []
for a, b in queries:
if a in graph and b in graph[a]:
result.append(graph[a][b])
else:
result.append(-1.0)
return resultWhere is the number of unique strings and is the number of queries.
When building the graph, each equation a / b = value must create two edges: a -> b with weight value AND b -> a with weight 1/value. Forgetting the reciprocal edge means you can only traverse the relationship in one direction, causing valid queries to return -1 when the path exists in reverse.
A query might contain variables that never appeared in any equation. Before starting BFS/DFS, you must check if both the source and target variables exist in the graph. Attempting to traverse from a non-existent node will cause null pointer errors or infinite loops depending on your implementation.
In the weighted Union-Find approach, the weight calculation during union is subtle: when connecting rootX to rootY, the new weight for rootX must be value * weight[y] / weight[x]. Getting this formula wrong (such as dividing instead of multiplying, or using the wrong variables) will produce incorrect ratios for subsequent queries.