399. 除法求值
题目描述
输入已知条件 equations 和 values:equations[i] = [A, B] 与 values[i] 一起表示 A / B = values[i]。每个变量是一个字符串。
另给若干询问 queries,queries[j] = [C, D] 要求计算 C / D 的值。
返回一个与 queries 等长的浮点数数组,按询问顺序给出答案。如果某个结果无法由已知条件推出,答案记为 -1.0。如果询问里出现了已知条件中从未出现的变量,也记为 -1.0,哪怕询问的是它除以自己。
输入保证有效:不会出现除以 0,已知条件之间也不会互相矛盾。
输入:equations = [["a","b"],["b","c"]] values = [2.0,3.0] queries = [["a","c"],["b","a"],["a","e"],["a","a"],["x","x"]] 输出:[6.0,0.5,-1.0,1.0,-1.0]
解释:由 a / b = 2、b / c = 3 推出 a / c = 6、b / a = 0.5;e 和 x 未定义,结果为 -1.0。
输入:equations = [["a","b"],["b","c"],["bc","cd"]] values = [1.5,2.5,5.0] queries = [["a","c"],["c","b"],["bc","cd"],["cd","bc"]] 输出:[3.75,0.4,5.0,0.2]
解释:a / c = 1.5 × 2.5 = 3.75,c / b = 1 / 2.5 = 0.4。
输入:equations = [["a","b"]] values = [0.5] queries = [["a","b"],["b","a"],["a","c"],["x","y"]] 输出:[0.5,2.0,-1.0,-1.0]
解释:c、x、y 没有出现在已知条件里。
数据范围
1 <= equations.length <= 20equations[i].length == 21 <= A.length, B.length <= 5values.length == equations.length0.0 < values[i] <= 20.01 <= queries.length <= 20queries[i].length == 21 <= C.length, D.length <= 5- 变量名只含小写英文字母和数字
解题思路:建图 + 广度优先搜索
把每个变量看作图的节点,A / B = v 建两条有向边:A→B 权 v,B→A 权 1/v。求 C / D 就是从 C 出发找一条到 D 的路径,把沿途边权乘起来:(C/X)·(X/Y)·(Y/D) = C/D。每个查询做一次 BFS,队列里带上到当前节点的乘积。变量没出现过或不连通就是 -1。
为什么正确
队列中的 (node, prod) 满足 prod = src / node。
复杂度分析
每个查询 O(V + E),总时间 O(Q·(V + E));空间 O(V + E)。
易错点
未出现过的变量即使是 x / x 也要返回 -1.0,必须先判断是否在图中;反向边的权是倒数 1/v,不是 v。
参考代码
Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。
class Solution:
def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
graph = {}
for (a, b), v in zip(equations, values):
graph.setdefault(a, {})[b] = v
graph.setdefault(b, {})[a] = 1 / v
ans = []
for src, dst in queries:
if src not in graph or dst not in graph:
ans.append(-1.0)
continue
seen = {src}
queue = [(src, 1.0)]
res = -1.0
head = 0
while head < len(queue):
node, prod = queue[head]
head += 1
if node == dst:
res = prod
break
for nxt, w in graph[node].items():
if nxt not in seen:
seen.add(nxt)
queue.append((nxt, prod * w))
ans.append(res)
return ansclass Solution {
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
Map<String, Map<String, Double>> graph = new HashMap<>();
for (int i = 0; i < equations.size(); i++) {
String a = equations.get(i).get(0), b = equations.get(i).get(1);
graph.computeIfAbsent(a, k -> new HashMap<>()).put(b, values[i]);
graph.computeIfAbsent(b, k -> new HashMap<>()).put(a, 1.0 / values[i]);
}
double[] ans = new double[queries.size()];
int qi = 0;
for (List<String> q : queries) {
String src = q.get(0), dst = q.get(1);
if (!graph.containsKey(src) || !graph.containsKey(dst)) {
ans[qi++] = -1.0;
continue;
}
Set<String> seen = new HashSet<>();
seen.add(src);
List<String> qn = new ArrayList<>();
List<Double> qp = new ArrayList<>();
qn.add(src);
qp.add(1.0);
double res = -1.0;
int head = 0;
while (head < qn.size()) {
String node = qn.get(head);
double prod = qp.get(head);
head++;
if (node.equals(dst)) {
res = prod;
break;
}
for (Map.Entry<String, Double> e : graph.get(node).entrySet()) {
if (!seen.contains(e.getKey())) {
seen.add(e.getKey());
qn.add(e.getKey());
qp.add(prod * e.getValue());
}
}
}
ans[qi++] = res;
}
return ans;
}
}class Solution {
public:
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
unordered_map<string, unordered_map<string, double>> graph;
for (int i = 0; i < (int)equations.size(); i++) {
graph[equations[i][0]][equations[i][1]] = values[i];
graph[equations[i][1]][equations[i][0]] = 1.0 / values[i];
}
vector<double> ans;
for (auto& q : queries) {
const string& src = q[0];
const string& dst = q[1];
if (!graph.count(src) || !graph.count(dst)) {
ans.push_back(-1.0);
continue;
}
unordered_set<string> seen = {src};
vector<pair<string, double>> queue = {{src, 1.0}};
double res = -1.0;
size_t head = 0;
while (head < queue.size()) {
auto [node, prod] = queue[head];
head++;
if (node == dst) {
res = prod;
break;
}
for (auto& [nxt, w] : graph[node]) {
if (!seen.count(nxt)) {
seen.insert(nxt);
queue.push_back({nxt, prod * w});
}
}
}
ans.push_back(res);
}
return ans;
}
};func calcEquation(equations [][]string, values []float64, queries [][]string) []float64 {
graph := map[string]map[string]float64{}
for i, e := range equations {
if graph[e[0]] == nil {
graph[e[0]] = map[string]float64{}
}
graph[e[0]][e[1]] = values[i]
if graph[e[1]] == nil {
graph[e[1]] = map[string]float64{}
}
graph[e[1]][e[0]] = 1 / values[i]
}
ans := []float64{}
for _, q := range queries {
src, dst := q[0], q[1]
_, ok1 := graph[src]
_, ok2 := graph[dst]
if !ok1 || !ok2 {
ans = append(ans, -1.0)
continue
}
seen := map[string]bool{src: true}
type item struct {
node string
prod float64
}
queue := []item{{src, 1.0}}
res := -1.0
head := 0
for head < len(queue) {
cur := queue[head]
head++
if cur.node == dst {
res = cur.prod
break
}
for nxt, w := range graph[cur.node] {
if !seen[nxt] {
seen[nxt] = true
queue = append(queue, item{nxt, cur.prod * w})
}
}
}
ans = append(ans, res)
}
return ans
}var calcEquation = function(equations, values, queries) {
const graph = new Map();
for (let i = 0; i < equations.length; i++) {
const [a, b] = equations[i];
if (!graph.has(a)) graph.set(a, new Map());
graph.get(a).set(b, values[i]);
if (!graph.has(b)) graph.set(b, new Map());
graph.get(b).set(a, 1 / values[i]);
}
const ans = [];
for (const [src, dst] of queries) {
if (!graph.has(src) || !graph.has(dst)) {
ans.push(-1.0);
continue;
}
const seen = new Set([src]);
const queue = [[src, 1.0]];
let res = -1.0;
let head = 0;
while (head < queue.length) {
const [node, prod] = queue[head];
head++;
if (node === dst) {
res = prod;
break;
}
for (const [nxt, w] of graph.get(node)) {
if (!seen.has(nxt)) {
seen.add(nxt);
queue.push([nxt, prod * w]);
}
}
}
ans.push(res);
}
return ans;
};function calcEquation(equations: string[][], values: number[], queries: string[][]): number[] {
const graph = new Map<string, Map<string, number>>();
for (let i = 0; i < equations.length; i++) {
const [a, b] = equations[i];
if (!graph.has(a)) graph.set(a, new Map());
graph.get(a)!.set(b, values[i]);
if (!graph.has(b)) graph.set(b, new Map());
graph.get(b)!.set(a, 1 / values[i]);
}
const ans: number[] = [];
for (const [src, dst] of queries) {
if (!graph.has(src) || !graph.has(dst)) {
ans.push(-1.0);
continue;
}
const seen = new Set([src]);
const queue: [string, number][] = [[src, 1.0]];
let res = -1.0;
let head = 0;
while (head < queue.length) {
const [node, prod] = queue[head];
head++;
if (node === dst) {
res = prod;
break;
}
for (const [nxt, w] of graph.get(node)!) {
if (!seen.has(nxt)) {
seen.add(nxt);
queue.push([nxt, prod * w]);
}
}
}
ans.push(res);
}
return ans;
}其他解法
广度优先搜索每次查询都重新搜图,实现直观;Floyd 先算出所有点对的比值,查询最快但预处理是立方级;带权并查集在合并与查找时维护比值,整体效率最高。
解法 2:Floyd 预处理 时间 O(n³ + q),空间 O(n²)
设 n 为变量个数,e 为等式个数,q 为查询个数。把变量编号,用矩阵 d 记录已知比值,d[i][i] = 1,未知记为 -1。按 Floyd 算法枚举中转点 k,若 d[i][k] 和 d[k][j] 都已知,就令 d[i][j] = d[i][k]·d[k][j]。查询时直接读矩阵,变量不存在时返回 -1。
预处理后每次查询 O(1),适合查询很多、变量较少的情况。变量多时 O(n³) 预处理和 O(n²) 空间代价很大。
class Solution:
def calcEquation(self, equations, values, queries):
idx = {}
for a, b in equations:
for x in (a, b):
if x not in idx:
idx[x] = len(idx)
n = len(idx)
d = [[-1.0] * n for _ in range(n)]
for i in range(n):
d[i][i] = 1.0
for (a, b), v in zip(equations, values):
d[idx[a]][idx[b]] = v
d[idx[b]][idx[a]] = 1 / v
for k in range(n):
for i in range(n):
if d[i][k] < 0:
continue
for j in range(n):
if d[k][j] > 0 and d[i][j] < 0:
d[i][j] = d[i][k] * d[k][j]
ans = []
for a, b in queries:
if a not in idx or b not in idx:
ans.append(-1.0)
else:
ans.append(d[idx[a]][idx[b]])
return ansclass Solution {
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
Map<String, Integer> idx = new HashMap<>();
for (List<String> e : equations)
for (String x : e)
if (!idx.containsKey(x)) idx.put(x, idx.size());
int n = idx.size();
double[][] d = new double[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(d[i], -1.0);
d[i][i] = 1.0;
}
for (int i = 0; i < equations.size(); i++) {
int a = idx.get(equations.get(i).get(0)), b = idx.get(equations.get(i).get(1));
d[a][b] = values[i];
d[b][a] = 1.0 / values[i];
}
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++) {
if (d[i][k] < 0) continue;
for (int j = 0; j < n; j++)
if (d[k][j] > 0 && d[i][j] < 0) d[i][j] = d[i][k] * d[k][j];
}
double[] ans = new double[queries.size()];
for (int qi = 0; qi < queries.size(); qi++) {
String a = queries.get(qi).get(0), b = queries.get(qi).get(1);
if (!idx.containsKey(a) || !idx.containsKey(b)) ans[qi] = -1.0;
else ans[qi] = d[idx.get(a)][idx.get(b)];
}
return ans;
}
}class Solution {
public:
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
unordered_map<string, int> idx;
for (auto& e : equations)
for (auto& x : e)
if (!idx.count(x)) { int id = idx.size(); idx[x] = id; }
int n = idx.size();
vector<vector<double>> d(n, vector<double>(n, -1.0));
for (int i = 0; i < n; i++) d[i][i] = 1.0;
for (int i = 0; i < (int)equations.size(); i++) {
int a = idx[equations[i][0]], b = idx[equations[i][1]];
d[a][b] = values[i];
d[b][a] = 1.0 / values[i];
}
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++) {
if (d[i][k] < 0) continue;
for (int j = 0; j < n; j++)
if (d[k][j] > 0 && d[i][j] < 0) d[i][j] = d[i][k] * d[k][j];
}
vector<double> ans;
for (auto& q : queries) {
if (!idx.count(q[0]) || !idx.count(q[1])) ans.push_back(-1.0);
else ans.push_back(d[idx[q[0]]][idx[q[1]]]);
}
return ans;
}
};func calcEquation(equations [][]string, values []float64, queries [][]string) []float64 {
idx := map[string]int{}
for _, e := range equations {
for _, x := range e {
if _, ok := idx[x]; !ok {
idx[x] = len(idx)
}
}
}
n := len(idx)
d := make([][]float64, n)
for i := range d {
d[i] = make([]float64, n)
for j := range d[i] {
d[i][j] = -1.0
}
d[i][i] = 1.0
}
for i, e := range equations {
a, b := idx[e[0]], idx[e[1]]
d[a][b] = values[i]
d[b][a] = 1 / values[i]
}
for k := 0; k < n; k++ {
for i := 0; i < n; i++ {
if d[i][k] < 0 {
continue
}
for j := 0; j < n; j++ {
if d[k][j] > 0 && d[i][j] < 0 {
d[i][j] = d[i][k] * d[k][j]
}
}
}
}
ans := []float64{}
for _, q := range queries {
a, ok1 := idx[q[0]]
b, ok2 := idx[q[1]]
if !ok1 || !ok2 {
ans = append(ans, -1.0)
} else {
ans = append(ans, d[a][b])
}
}
return ans
}var calcEquation = function(equations, values, queries) {
const idx = new Map();
for (const e of equations)
for (const x of e)
if (!idx.has(x)) idx.set(x, idx.size);
const n = idx.size;
const d = Array.from({ length: n }, () => new Array(n).fill(-1));
for (let i = 0; i < n; i++) d[i][i] = 1;
for (let i = 0; i < equations.length; i++) {
const a = idx.get(equations[i][0]), b = idx.get(equations[i][1]);
d[a][b] = values[i];
d[b][a] = 1 / values[i];
}
for (let k = 0; k < n; k++)
for (let i = 0; i < n; i++) {
if (d[i][k] < 0) continue;
for (let j = 0; j < n; j++)
if (d[k][j] > 0 && d[i][j] < 0) d[i][j] = d[i][k] * d[k][j];
}
const ans = [];
for (const [a, b] of queries) {
if (!idx.has(a) || !idx.has(b)) ans.push(-1.0);
else ans.push(d[idx.get(a)][idx.get(b)]);
}
return ans;
};function calcEquation(equations: string[][], values: number[], queries: string[][]): number[] {
const idx = new Map<string, number>();
for (const e of equations)
for (const x of e)
if (!idx.has(x)) idx.set(x, idx.size);
const n = idx.size;
const d: number[][] = Array.from({ length: n }, () => new Array<number>(n).fill(-1));
for (let i = 0; i < n; i++) d[i][i] = 1;
for (let i = 0; i < equations.length; i++) {
const a = idx.get(equations[i][0])!, b = idx.get(equations[i][1])!;
d[a][b] = values[i];
d[b][a] = 1 / values[i];
}
for (let k = 0; k < n; k++)
for (let i = 0; i < n; i++) {
if (d[i][k] < 0) continue;
for (let j = 0; j < n; j++)
if (d[k][j] > 0 && d[i][j] < 0) d[i][j] = d[i][k] * d[k][j];
}
const ans: number[] = [];
for (const [a, b] of queries) {
if (!idx.has(a) || !idx.has(b)) ans.push(-1.0);
else ans.push(d[idx.get(a)!][idx.get(b)!]);
}
return ans;
}解法 3:带权并查集 时间 O((e + q)·α(n)),空间 O(n)
每个变量记录父节点 parent 和权值 weight,weight[x] 表示 x 除以 parent[x] 的值。查找根时做路径压缩,并把沿途权值相乘。合并 a / b = v 时,把 a 的根挂到 b 的根下,权值设为 v·weight[b] / weight[a]。查询时两个变量同根则答案是 weight[a] / weight[b],否则返回 -1。
建图和查询都接近线性,是三种方法里最快的。权值随路径压缩更新的推导是难点,写错不易发现。
class Solution:
def calcEquation(self, equations, values, queries):
parent = {}
weight = {}
def find(x):
if parent[x] != x:
root = find(parent[x])
weight[x] *= weight[parent[x]]
parent[x] = root
return parent[x]
for (a, b), v in zip(equations, values):
for x in (a, b):
if x not in parent:
parent[x] = x
weight[x] = 1.0
ra, rb = find(a), find(b)
if ra != rb:
parent[ra] = rb
weight[ra] = v * weight[b] / weight[a]
ans = []
for a, b in queries:
if a not in parent or b not in parent or find(a) != find(b):
ans.append(-1.0)
else:
ans.append(weight[a] / weight[b])
return ansclass Solution {
Map<String, String> parent = new HashMap<>();
Map<String, Double> weight = new HashMap<>();
String find(String x) {
String p = parent.get(x);
if (!p.equals(x)) {
String root = find(p);
weight.put(x, weight.get(x) * weight.get(p));
parent.put(x, root);
}
return parent.get(x);
}
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
for (int i = 0; i < equations.size(); i++) {
String a = equations.get(i).get(0), b = equations.get(i).get(1);
for (String x : new String[]{a, b})
if (!parent.containsKey(x)) { parent.put(x, x); weight.put(x, 1.0); }
String ra = find(a), rb = find(b);
if (!ra.equals(rb)) {
parent.put(ra, rb);
weight.put(ra, values[i] * weight.get(b) / weight.get(a));
}
}
double[] ans = new double[queries.size()];
for (int qi = 0; qi < queries.size(); qi++) {
String a = queries.get(qi).get(0), b = queries.get(qi).get(1);
if (!parent.containsKey(a) || !parent.containsKey(b) || !find(a).equals(find(b))) ans[qi] = -1.0;
else ans[qi] = weight.get(a) / weight.get(b);
}
return ans;
}
}class Solution {
public:
unordered_map<string, string> parent;
unordered_map<string, double> weight;
string find(const string& x) {
if (parent[x] != x) {
string p = parent[x];
string root = find(p);
weight[x] *= weight[p];
parent[x] = root;
}
return parent[x];
}
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
for (int i = 0; i < (int)equations.size(); i++) {
const string& a = equations[i][0];
const string& b = equations[i][1];
for (const string& x : {a, b})
if (!parent.count(x)) { parent[x] = x; weight[x] = 1.0; }
string ra = find(a), rb = find(b);
if (ra != rb) {
parent[ra] = rb;
weight[ra] = values[i] * weight[b] / weight[a];
}
}
vector<double> ans;
for (auto& q : queries) {
if (!parent.count(q[0]) || !parent.count(q[1]) || find(q[0]) != find(q[1])) ans.push_back(-1.0);
else ans.push_back(weight[q[0]] / weight[q[1]]);
}
return ans;
}
};func calcEquation(equations [][]string, values []float64, queries [][]string) []float64 {
parent := map[string]string{}
weight := map[string]float64{}
var find func(x string) string
find = func(x string) string {
p := parent[x]
if p != x {
root := find(p)
weight[x] *= weight[p]
parent[x] = root
}
return parent[x]
}
for i, e := range equations {
a, b := e[0], e[1]
for _, x := range []string{a, b} {
if _, ok := parent[x]; !ok {
parent[x] = x
weight[x] = 1.0
}
}
ra, rb := find(a), find(b)
if ra != rb {
parent[ra] = rb
weight[ra] = values[i] * weight[b] / weight[a]
}
}
ans := []float64{}
for _, q := range queries {
_, ok1 := parent[q[0]]
_, ok2 := parent[q[1]]
if !ok1 || !ok2 || find(q[0]) != find(q[1]) {
ans = append(ans, -1.0)
} else {
ans = append(ans, weight[q[0]]/weight[q[1]])
}
}
return ans
}var calcEquation = function(equations, values, queries) {
const parent = new Map(), weight = new Map();
const find = (x) => {
const p = parent.get(x);
if (p !== x) {
const root = find(p);
weight.set(x, weight.get(x) * weight.get(p));
parent.set(x, root);
}
return parent.get(x);
};
for (let i = 0; i < equations.length; i++) {
const [a, b] = equations[i];
for (const x of [a, b])
if (!parent.has(x)) { parent.set(x, x); weight.set(x, 1.0); }
const ra = find(a), rb = find(b);
if (ra !== rb) {
parent.set(ra, rb);
weight.set(ra, values[i] * weight.get(b) / weight.get(a));
}
}
const ans = [];
for (const [a, b] of queries) {
if (!parent.has(a) || !parent.has(b) || find(a) !== find(b)) ans.push(-1.0);
else ans.push(weight.get(a) / weight.get(b));
}
return ans;
};function calcEquation(equations: string[][], values: number[], queries: string[][]): number[] {
const parent = new Map<string, string>(), weight = new Map<string, number>();
const find = (x: string): string => {
const p = parent.get(x)!;
if (p !== x) {
const root = find(p);
weight.set(x, weight.get(x)! * weight.get(p)!);
parent.set(x, root);
}
return parent.get(x)!;
};
for (let i = 0; i < equations.length; i++) {
const [a, b] = equations[i];
for (const x of [a, b])
if (!parent.has(x)) { parent.set(x, x); weight.set(x, 1.0); }
const ra = find(a), rb = find(b);
if (ra !== rb) {
parent.set(ra, rb);
weight.set(ra, values[i] * weight.get(b)! / weight.get(a)!);
}
}
const ans: number[] = [];
for (const [a, b] of queries) {
if (!parent.has(a) || !parent.has(b) || find(a) !== find(b)) ans.push(-1.0);
else ans.push(weight.get(a)! / weight.get(b)!);
}
return ans;
}