1.题目描述
给你一个变量对数组
equations
和一个实数值数组values
作为已知条件,其中equations[i] = [Ai, Bi]
和values[i]
共同表示等式Ai / Bi = values[i]
。每个Ai
或Bi
是一个表示单个变量的字符串。另有一些以数组
queries
表示的问题,其中queries[j] = [Cj, Dj]
表示第j
个问题,请你根据已知条件找出Cj / Dj = ?
的结果作为答案。返回 所有问题的答案 。如果存在某个无法确定的答案,则用
-1.0
替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用-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 ] 注意:x 是未定义的 => -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
由小写英文字母与数字组成
2.解题思路
由题意得:a / b = 2, b / c = 3 => a / c = 6
我们需要一个数据结构,它能够通过father[x]找到它的父节点,并且通过value[x]找到它与父节点之间的倍数关系;这样的话,对于查询中的[x,y],如果这两个数位于同一个连通图中,且具有相同的父节点,我们就可以通过value[y] / value[x]来获取这两数的商,因为共同将其转为和根节点之间的倍数关系,两个数就可以直接相除,得到结果。
这个数据结构就是并查集,
它的add方法用于向father/value中加入x,初始时father[x]为空,value[x] = 1;
它有一个find方法用于先找到这个结点的根节点root,再依次将该结点x到根节点root之间所有的结点的value都置为与root的倍数关系,这样value[root] = 1,例如a/b = 2,==>father[b] = a, value[b] = 2,表示a,b之间有2倍的关系,但最终求b / c 时,要用value[c] / value[b],因为这里是把被除数当作根,所以结果应该取倒数
merge方法用于合并两个不相连的被除数/除数,将两个数按照倍数关系加入到father/value中
3.代码实现
/**
* @description: 除法求值
* 使用并查集-建立每个点与根节点的系数关系
* 如果两个点位于同一个连通图,则可以通过一步除法操作获取答案
* @author: lzy
* @time: 2024/8/29 10:55
*/
class UnionFind {
//记录每个节点的根节点
public Map<String,String> father = null;
//记录结点与根节点的系数
public Map<String,Double> value = null;
public UnionFind() {
father = new HashMap<>();
value = new HashMap<>();
}
/**
* 新增结点
* @param x
*/
public void add(String x) {
if (!father.containsKey(x)) {
father.put(x,null);
value.put(x,1.0);
}
}
/**
* 1.查找当前结点的根; 2.压缩当前结点到根节点路径上所有结点,使其father,value转为根节点的关系
*/
public String find(String x) {
String root = x;
double base = 1.0;
//root为x的根结点
while (father.get(root) != null) {
root = father.get(root);
base *= value.get(root);
}
//压缩x到根路径上 每个节点与根的系数关系
while (!x.equals(root)) {
String originFather = father.get(x);
father.put(x,root);
value.put(x,value.get(x) * base);
x = originFather;
base /= value.get(originFather);
}
return root;
}
/**
* merge (a,b) 2
* 合并两个点 => 令father[b] = a, value[b] = 2
*/
public void merge(String x, String y, double val) {
String root_x = find(x);
String root_y = find(y);
if (!root_x.equals(root_y)) {
father.put(root_y,root_x);
value.put(root_y,val * value.get(x) / value.get(y));
}
}
/**
* 判断两个点是否联通
*/
public boolean isConnected(String x, String y) {
return value.containsKey(x) && value.containsKey(y) && find(x).equals(find(y));
}
}
class Solution {
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
int n = queries.size();
double[] res = new double[n];
Arrays.fill(res,-1.0);
UnionFind uf = new UnionFind();
for (int i = 0; i < equations.size(); i++) {
String x = equations.get(i).get(0);
String y = equations.get(i).get(1);
uf.add(x);
uf.add(y);
uf.merge(x,y,values[i]);
}
for (int i = 0; i < n; i++) {
String x = queries.get(i).get(0);
String y = queries.get(i).get(1);
if (uf.isConnected(x,y)) {
res[i] = uf.value.get(y) / uf.value.get(x);
}
}
return res;
}
}