399. 除法求值
✨核心逻辑
本题可以转换为 图论 + BFS(广度优先搜索) 的问题:
- 构建加权有向图:将每个变量看作图中的一个节点,除法关系看作两个节点之间的有向边,
values[i]就是边的权重。例如a / b = 2.0,说明a -> b的权重为2.0,同时b -> a的权重为1 / 2.0。 - BFS 求解路径权重乘积:对于每一个查询
[C, D],我们需要在构建好的图中从起点C出发,使用 BFS 寻找一条到达终点D的路径。路径上所有边的权重乘积,就是C / D的结果。 - 特殊结果处理:如果图中不包含
C或D,直接返回-1.0;如果C == D,直接返回1.0。
🔥代码实现(含详细变量注释)
class Solution {
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
// 1. 构建邻接表(图)
// graph 的 Key 是节点名称(变量),Value 是一个 Map,存储它连接到的邻居节点以及对应的边权重(除法结果)
Map<String, Map<String, Double>> graph = new HashMap<>();
// i:遍历整个 equations 集合,为每个等式构建双向边
for (int i = 0; i < equations.size(); i++) {
// left:当前等式被除数变量
String left = equations.get(i).get(0);
// right:当前等式除数变量
String right = equations.get(i).get(1);
// num:当前等式的结果(即 left / right 的值)
double num = values[i];
// 建立 left -> right 的边,权重为 num
graph.computeIfAbsent(left, m -> new HashMap()).put(right, num);
// 建立 right -> left 的边,权重为 1 / num(因为 right / left 是倒数关系)
graph.computeIfAbsent(right, m -> new HashMap()).put(left, 1.0 / num);
}
// result:用于存储最终每个查询的结果数组
double[] result = new double[queries.size()];
// 2. 处理查询
for (int i = 0; i < queries.size(); i++) {
// left:当前查询的起点变量
String left = queries.get(i).get(0);
// right:当前查询的终点变量
String right = queries.get(i).get(1);
// 如果图中不存在 left 或 right 变量,该查询无法计算,直接置为 -1.0
if (!graph.containsKey(left) || !graph.containsKey(right)) {
result[i] = -1.0;
continue;
}
// 如果起点和终点相同,结果必定为 1.0
if (left.equals(right)) {
result[i] = 1.0;
continue;
}
// 调用 BFS 方法在图中寻找路径并计算乘积结果
result[i] = bfs(graph, left, right);
}
return result;
}
// bfs:广度优先搜索辅助函数,用于计算从 start 到 end 的路径乘积
private double bfs(Map<String, Map<String, Double>> graph, String start, String end) {
// queue:用于 BFS 遍历的队列,存放即将探索的节点
Queue<String> queue = new LinkedList<>();
// map:记录已经访问过的节点,以及从起点 start 到该节点的累积乘积结果
Map<String, Double> map = new HashMap<>();
queue.offer(start);
map.put(start, 1.0);
while (!queue.isEmpty()) {
// 从队列中弹出一个当前正在访问的节点
String current = queue.poll();
// currentVal:从起点 start 到当前节点 current 的累积乘积值
double currentVal = map.get(current);
// 如果当前节点就是终点,直接返回累积乘积结果
if (current.equals(end)) {
return currentVal;
}
// 遍历当前节点连接的所有邻居节点
for (Map.Entry<String, Double> neighbor : graph.get(current).entrySet()) {
// k:当前邻居节点的名称(变量名)
String k = neighbor.getKey();
// v:从当前节点 current 到邻居节点 k 的边的权重
double v = neighbor.getValue();
// 如果该邻居节点已经在 map 中访问过,跳过避免死循环
if (map.containsKey(k)) {
continue;
}
// 将邻居节点放入队列,等待后续探索
queue.offer(k);
// 记录从起点 start 到达邻居节点 k 的累积乘积值(当前累积值 * 当前边权重 v)
map.put(k, currentVal * v);
}
}
// 如果队列遍历完都没有找到终点,说明两点不可达,返回 -1.0
return -1.0;
}
}
⏱️复杂度分析
时间复杂度:O(Q * (V + E)),其中 Q 是查询的数量,V 是图中变量节点的数量(最多为 2 * equations.size()),E 是边数。构建图的时间为 O(E),对于每一个查询,BFS 最多遍历图中的所有节点和边一次,因此单次查询为 O(V + E)。
空间复杂度:O(V + E)。主要消耗在于使用邻接表 graph 来存储构建的图数据结构,包含了所有的节点和双向边映射关系。
🔍总结一下:
currentVal 是拿出此时出队的值,如果和 end 一样就返回,那么入队时 map 里 put 的是此元素(未包含)的值,只有这样,此元素与 end 相等时拿到的才是计算过的最终结果