题目来源:LeetCode 399. 除法求值
问题抽象: 给定一组除法等式 equations(每个等式为 [a_i, b_i, val_i] 表示 a_i / b_i = val_i)和一组查询 queries(每个查询为 [c_j, d_j]),需计算每个查询 c_j / d_j 的结果,满足以下核心需求:
-
计算规则:
- 利用已知等式 推导未知查询(如已知
a/b=2.0和b/c=3.0,则a/c = 2.0×3.0=6.0); - 若查询变量无关联或未定义,返回
-1.0。
- 利用已知等式 推导未知查询(如已知
-
图论建模:
- 有向带权图:变量为节点,边
a→b权重val(表示a/b=val),边b→a权重1/val; - 路径计算:查询
c_j/d_j等价于从c_j到d_j的路径权重乘积(DFS 或 BFS 遍历)。
- 有向带权图:变量为节点,边
-
边界处理:
- 变量未定义:若查询中的变量未出现在等式中,返回
-1.0; - 相同变量查询:
c_j = d_j时返回1.0(即使变量未定义); - 无连通路径:若变量间无路径连接,返回
-1.0; - 零除保护:输入保证等式
val_i > 0(无非正数权重)。
- 变量未定义:若查询中的变量未出现在等式中,返回
-
优化目标:
- 避免对每个查询独立遍历全图(时间复杂度
O(q·n)不可接受,q为查询数,n为变量数); - 预处理建图后,使用 并查集 或 Floyd 算法 预计算所有变量对的最短路径权重。
- 避免对每个查询独立遍历全图(时间复杂度
-
计算约束:
- 时间复杂度 O(n³ + q):
- Floyd 算法
O(n³)预计算所有节点对的最短路径权重; - 查询响应
O(1);
- Floyd 算法
- 空间复杂度 O(n²):存储所有节点对的权重矩阵;
- 输入规模:
- 变量数
n ≤ 20(等式数量≤ 20); - 查询数
q ≤ 20。
- 变量数
- 时间复杂度 O(n³ + q):
输入:等式列表 equations(每个元素 [a_i, b_i, val_i],a_i, b_i 为字符串,val_i > 0);查询列表 queries(格式同 equations 但无权重)。
输出:浮点数数组 ans(ans[j] 为查询 queries[j] 的结果,无解时 -1.0)。
解题思路
该问题可以建模为带权图问题,使用带权并查集高效解决:
- 图建模:每个变量为节点,方程式
A/B = v表示节点A到B的有向边(权重v),反向边权重为1/v。 - 并查集设计:
parent[]:记录每个节点的根节点。weight[]:记录节点到根节点的权重乘积(节点值 = 根节点值 × 权重)。
- 核心操作:
- 查找:递归寻找根节点,同时进行路径压缩和权重更新(当前权重 = 当前权重 × 父节点权重)。
- 合并:若
A/B = v,找到根节点rootA和rootB。若不同根,将rootA的父节点设为rootB,并更新权重为(v × weight[B]) / weight[A]。
- 查询处理:对于查询
C/D:- 若
C或D未出现,返回-1.0。 - 若同根,结果为
weight[C] / weight[D];否则返回-1.0。
- 若
代码实现(Java版)🔥点击下载源码
class Solution {
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
// 映射变量名到唯一ID
Map<String, Integer> varToId = new HashMap<>();
int id = 0;
for (List<String> eq : equations) {
for (String var : eq) {
if (!varToId.containsKey(var)) {
varToId.put(var, id++);
}
}
}
// 初始化并查集
int n = varToId.size();
int[] parent = new int[n];
double[] weight = new double[n]; // 节点到父节点的权重
for (int i = 0; i < n; i++) {
parent[i] = i;
weight[i] = 1.0;
}
// 合并操作:处理方程式
for (int i = 0; i < equations.size(); i++) {
List<String> eq = equations.get(i);
int aId = varToId.get(eq.get(0));
int bId = varToId.get(eq.get(1));
double value = values[i];
int rootA = find(parent, weight, aId);
int rootB = find(parent, weight, bId);
if (rootA != rootB) {
parent[rootA] = rootB; // 合并根节点
weight[rootA] = (value * weight[bId]) / weight[aId]; // 更新权重
}
}
// 处理查询
double[] res = new double[queries.size()];
for (int i = 0; i < queries.size(); i++) {
List<String> query = queries.get(i);
String c = query.get(0);
String d = query.get(1);
if (!varToId.containsKey(c) || !varToId.containsKey(d)) {
res[i] = -1.0;
continue;
}
int cId = varToId.get(c);
int dId = varToId.get(d);
int rootC = find(parent, weight, cId);
int rootD = find(parent, weight, dId);
if (rootC != rootD) {
res[i] = -1.0; // 不连通
} else {
res[i] = weight[cId] / weight[dId]; // 权重比值
}
}
return res;
}
// 查找根节点,同时路径压缩和更新权重
private int find(int[] parent, double[] weight, int x) {
if (parent[x] != x) {
int origin = parent[x];
parent[x] = find(parent, weight, parent[x]); // 递归找根
weight[x] *= weight[origin]; // 更新权重:当前节点权重 × 父节点权重
}
return parent[x];
}
}
代码说明
- 变量映射:使用
HashMap将字符串变量名映射为整数ID,便于并查集操作。 - 并查集初始化:每个节点初始父节点为自身,初始权重为
1.0。 - 合并操作:
- 对于每个方程式
A/B = v,找到A和B的根节点。 - 若根节点不同,将
A的根节点指向B的根节点,并更新权重为(v × weight[B]) / weight[A](由数学关系推导)。
- 对于每个方程式
- 查询处理:
- 检查变量是否存在,不存在直接返回
-1.0。 - 若变量同根,结果 =
weight[C] / weight[D](因C = weight[C] × root,D = weight[D] × root)。
- 检查变量是否存在,不存在直接返回
- 查找优化:在
find()函数中递归压缩路径,同时更新权重(当前节点权重乘父节点权重),确保后续查询高效。 - 复杂度:预处理 O ( N α ( N ) ) O(Nα(N)) O(Nα(N)),查询 O ( Q α ( N ) ) O(Qα(N)) O(Qα(N))( N N N 为变量数, Q Q Q 为查询数, α α α 为反阿克曼函数,接近常数)。
提交详情(执行用时、内存消耗)

724

被折叠的 条评论
为什么被折叠?



