Problem Statement
(Source) Equations are given in the format A / B = k
, where A
and B
are variables represented as strings, and k
is a real number (floating point number). Given some queries, return the answers. If the answer does not exist, return -1.0
.
Example:
Given a / b = 2.0, b / c = 3.0.
queries are: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ? .
return [6.0, 0.5, -1.0, 1.0, -1.0 ].
The input is: vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries
, where equations.size() == values.size()
, and the values are positive. This represents the equations. Return vector<double>
.
According to the example above:
equations = [ ["a", "b"], ["b", "c"] ], values = [2.0, 3.0], queries = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ].
The input is always valid. You may assume that evaluating the queries will result in no division by zero and there is no contradiction.
Solution 1 - Depth-first Search
Tags: Graph
.
class Solution(object):
def calcEquation(self, equations, values, queries):
"""
:type equations: List[List[str]]
:type values: List[float]
:type queries: List[List[str]]
:rtype: List[float]
"""
# Graph.
g = {}
n = len(values)
for i in xrange(n):
A, B = equations[i]
k = values[i]
g.setdefault(A, {})[B] = k
g.setdefault(B, {})[A] = 1 / k
# Depth-first Search.
from collections import deque
m = len(queries)
res = [-1.0] * m
for i in xrange(m):
A, B = queries[i]
if (A not in g) or (B not in g):
continue
elif A == B:
res[i] = 1
continue
sta = deque([A])
explored = set([A])
ans = 1.0
while sta:
top = sta[-1]
for C in g[top]:
y = g[top][C]
if C not in explored:
explored.add(C)
sta.append(C)
ans *= y
break
if sta[-1] == top:
y = sta.pop()
if sta:
x = sta[-1]
ans /= g[x][y]
else:
if sta[-1] == B:
res[i] = ans
break
return res
Complexity Analysis:
- Time Complexity:
O(mn)
, where
m
is the number of variables (or equations), and
n is the number of queries. - Space Complexity:
O(mn)
, where
m
is the number of variables (or equations), and
n is the number of queries.
Solution 2 - Amazing
A variation of Floyd–Warshall, computing quotients instead of shortest paths. An equation A/B=k is like a graph edge A->B, and (A/B)(B/C)(C/D) is like the path A->B->C->D. Submitted once, accepted in 35 ms (Credits @StephanPochmann).
def calcEquation(self, equations, values, queries):
quot = collections.defaultdict(dict)
for (num, den), val in zip(equations, values):
quot[num][num] = quot[den][den] = 1.0
quot[num][den] = val
quot[den][num] = 1 / val
for k, i, j in itertools.permutations(quot, 3):
if k in quot[i] and j in quot[k]:
quot[i][j] = quot[i][k] * quot[k][j]
return [quot[num].get(den, -1.0) for num, den in queries]
Variation without the if (submitted twice, accepted in 68 and 39 ms):
def calcEquation(self, equations, values, queries):
quot = collections.defaultdict(dict)
for (num, den), val in zip(equations, values):
quot[num][num] = quot[den][den] = 1.0
quot[num][den] = val
quot[den][num] = 1 / val
for k in quot:
for i in quot[k]:
for j in quot[k]:
quot[i][j] = quot[i][k] * quot[k][j]
return [quot[num].get(den, -1.0) for num, den in queries]
Could save a line with for i, j in itertools.permutations(quot[k], 2)
but it’s longer and I don’t like it as much here.
Explanation (Credits @ww123):
Nice solution. This is a floyd-warshall-y solution because of the triple nested loops. However it is difficult for me to understand it following the floyd-warshall reasoning. I will share my $.02 here. Correct me if I’m wrong.
Concretely the triple loops create a complete graph. For the first node, connect every pair of its neighbors, a complete sub-graph containing the first node. then the second, then the third, …. so after the loops, all related variables will be in one big complete graph.