leetcode:399. 除法求值
题目
给你一个变量对数组 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 的情况,且不存在任何矛盾的结果。
示例:
输入: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 ]
输入: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]
输入:equations = [["a","b"]], values = [0.5], queries = [["a","b"],["b","a"],["a","c"],["x","y"]]输出:[0.50000,2.00000,-1.00000,-1.00000]
解答 & 代码
变量之间的倍数关系具有传递性,处理有传递性的问题,可以使用“并查集”,需要在并查集的“合并”与“查询”操作中维护这些变量之间的倍数关系。
- 若两个变量同在一个集合(连通图)中,就能计算出它们的比值,即,可以将不同变量的比值转换成相同变量倍数的比值
- 若两个变量不在一个集合(连通图)中,则无法计算比值,返回 -1.0
并查集:
- 定义:
![[中等] 399. 除法求值 - 图1](/uploads/projects/liangduo-rjrcs@ggu4wq/3bcb12427e26cde09fe765f2d24651c4.png)
**int find(int x)**:递归函数,定义:查询节点 x 的根节点 id 并返回,寻找根节点的过程进行路径压缩,并更新权值
![[中等] 399. 除法求值 - 图2](/uploads/projects/liangduo-rjrcs@ggu4wq/76f56b3159c8c014cbff46fccaad5024.png)
**void _union(int x, int y, double value)**:合并操作:x 在一个连通图中,y 在一个连通图中,现有一条 x 指向 y 的路径,合并两个连通图
![[中等] 399. 除法求值 - 图3](/uploads/projects/liangduo-rjrcs@ggu4wq/2657a0ba302c7855c263d0f61d332c9b.png)
**isConnected(int x, int y)**:判定 x、y 是否在一个连通图内,返回 x/y 的结果- 若 x、y 根节点相同,说明在一个连通图内,返回
weight[x]/weight[y] 若 x、y 根节点不同,说明不在一个连通图内,无法计算结果,返回 -1.0 ```cpp class UnionFind { private: vector
parent; // 父节点数组,parent[i] 代表节点 i 的父节点 vector weight; // 指向父节点权值数组,weight[i] 代表节点 i 指向 parent[i] 的权值 public: /* 构造函数 - param n: 并查集的大小/节点数
*/
UnionFind(int n)
{
parent.resize(n);
weight.resize(n);
for(int i = 0; i < n; ++i)
{
} }parent[i] = i; // 将每个节点的父节点都初始化为自己本身weight[i] = 1.0; // 将每个节点指向父节点的权重都初始化为 1.0
/* 递归函数定义:查询节点 x 的根节点 id 并返回,寻找根节点的过程进行路径压缩,并更新权值
- 路径压缩指的是:将节点 x 及其各代祖先节点的父节点都设为根节点(最祖先的节点)
- param x: 节点 id
return: x 的根节点 id */ int find(int x) { // 递归结束条件:如果节点 x 的父节点是 x 本身,说明 x 已经是根节点,直接返回 if(x == parent[x])
return x;
int originParent = parent[x]; // 记录节点 x 原本的父节点 id // 递归寻找 parent[x] 的根节点,作为 x 的父节点。寻找的过程中进行路径压缩 parent[x] = find(parent[x]);
weight[x] *= weight[originParent]; // 更新节点 x 指向父节点(根节点)的权重 return parent[x]; }
/* 合并操作:x 在一个连通图中,y 在一个连通图中,现有一条 x 指向 y 的路径,合并两个连通图
- param x: 节点 x 的 id
- param y: 节点 y 的 id
param value: 节点 x 指向节点 y 的权重 */ void _union(int x, int y, double value) { int rootX = find(x); // 节点 x 的根节点 id(find() 查找过程中就会进行路径压缩) int rootY = find(y); // 节点 y 的根节点 id(find() 查找过程中就会进行路径压缩) // 如果节点 x、y 的根节点相同,则无需合并 if(rootX == rootY)
return;
// 令 x 的根节点指向 y 的根节点 parent[rootX] = rootY; weight[rootX] = value * weight[y] / weight[x]; // 更新 rootX->rootY 的权重 }
/* 判定 x、y 是否在一个连通图内,返回 x/y 的结果,
- 若无法确定答案(x、y 不在一个连通图内)则返回 -1.0
- param x: 分子
- param y: 分母
- return result: x/y 的结果,若无法确定答案则返回 -1.0
*/
double isConnected(int x, int y)
{
int rootX = find(x); // 节点 x 的根节点 id(find() 查找过程中就会进行路径压缩)
int rootY = find(y); // 节点 y 的根节点 id(find() 查找过程中就会进行路径压缩)
// 如果节点 x、y 的根节点相同,说明 x、y 在一个连通图内,则返回 x/y 的结果
if(rootX == rootY)
// 如果节点 x、y 的根节点不同,说明 x、y 不在一个连通图内,则无法确定答案,返回 -1.0 elsereturn weight[x] / weight[y];
} };return -1.0;
- param n: 并查集的大小/节点数
*/
UnionFind(int n)
{
parent.resize(n);
weight.resize(n);
for(int i = 0; i < n; ++i)
{
- 若 x、y 根节点相同,说明在一个连通图内,返回
class Solution {
public:
vector
// 1. 预处理:将变量字符串与 id 进行映射,存入哈希表(<变量字符串,id>)// 使得并查集的底层实现用数组实现,方便编码unordered_map<string, int> hashMap;int id = 0;for(int i = 0; i < equations.size(); ++i){string var1 = equations[i][0]; // 变量 1string var2 = equations[i][1]; // 变量 2// 插入哈希表if(hashMap.find(var1) == hashMap.end()){hashMap[var1] = id;++id;}if(hashMap.find(var2) == hashMap.end()){hashMap[var2] = id;++id;}// 通过 var1->var2 的边将 var1 的图和 var2 的图合并(构建并查集)unionFind._union(hashMap[var1], hashMap[var2], values[i]);}// 2. 做查询int queriesSize = queries.size();vector<double> result(queriesSize); // 结果数组for(int i = 0; i < queriesSize; ++i){string var1 = queries[i][0]; // 变量 1string var2 = queries[i][1]; // 变量 2// 若分子、分母的字符串至少有一个在 equations 未出现过,则直接返回 -1.0if(hashMap.find(var1) == hashMap.end() || hashMap.find(var2) == hashMap.end())result[i] = -1.0;elseresult[i] = unionFind.isConnected(hashMap[var1], hashMap[var2]);}return result;}
};
复杂度分析:设 `equations` 包含 N 对变量,其中包含 A 种不同的字符;设 `queries` 包含 Q 对变量- 时间复杂度 O((N + Q) logA):- 初始化并查集 O(N)- 构建并查集 O(N logA):遍历 `equations`,针对每对变量的边 `var1->var2`,调用 `_union()` 合并两个节点的图。`_union()` 的时间复杂度 O(logA)(`_union()` 中调用 `find()` 路径压缩的时间复杂度),共调用 N 次`_union()`- 查询并查集 O(Q logA):每次查询调用 `isConnected()` 的时间复杂度 O(logA)(`_union()` 中调用 `find()` 路径压缩的时间复杂度),共 Q 次查询> 路径压缩时间复杂度应该是 O(h),h 是树的高度,等于 O(logN),N 是节点数- 空间复杂度 O(N):- 哈希表长为 A- 并查集中数组 `parent` 和 `weight` 长为 N(初始化为 N,实际只用到 A)执行结果:
执行结果:通过
执行用时:4 ms, 在所有 C++ 提交中击败了 46.47% 的用户 内存消耗:7.9 MB, 在所有 C++ 提交中击败了 80.42% 的用户 ```
