自学内容网 自学内容网

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 的情况,且不存在任何矛盾的结果。

注意:未在等式列表中出现的变量是未定义的,因此无法确定它们的答案。

示例 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 由小写英文字母与数字组成

二、解题思路

  1. 将所有等式转化为图的形式,其中每个变量是一个节点,等式表示两个节点之间的有向边,边的权重为等式右边的值。

  2. 对于每个查询,可以将其看作在图中寻找一条从起点到终点的路径,路径上的边权重乘积即为所求的结果。

  3. 使用深度优先搜索(DFS)或广度优先搜索(BFS)在图中寻找这样的路径。如果在搜索过程中找到了终点,则返回路径上所有边的权重乘积;如果搜索完成后仍未找到终点,则返回-1.0。

  4. 在搜索过程中,为了避免重复搜索,需要记录已经访问过的节点。

三、具体代码

import java.util.*;

public class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        // 构建图
        Map<String, Map<String, Double>> graph = buildGraph(equations, values);
        
        // 结果数组
        double[] results = new double[queries.size()];
        
        // 遍历每个查询
        for (int i = 0; i < queries.size(); i++) {
            List<String> query = queries.get(i);
            String start = query.get(0);
            String end = query.get(1);
            
            // 如果查询中的变量未在图中出现,直接返回-1.0
            if (!graph.containsKey(start) || !graph.containsKey(end)) {
                results[i] = -1.0;
            } else if (start.equals(end)) { // 如果起点和终点相同,结果为1.0
                results[i] = 1.0;
            } else {
                // 使用DFS寻找路径
                Set<String> visited = new HashSet<>();
                results[i] = dfs(graph, start, end, 1.0, visited);
            }
        }
        
        return results;
    }
    
    private double dfs(Map<String, Map<String, Double>> graph, String current, String target, double value, Set<String> visited) {
        // 标记当前节点为已访问
        visited.add(current);
        
        // 如果找到了目标节点,返回累计的值
        if (graph.containsKey(current) && graph.get(current).containsKey(target)) {
            return value * graph.get(current).get(target);
        }
        
        // 遍历当前节点的邻接节点
        for (Map.Entry<String, Double> entry : graph.get(current).entrySet()) {
            String next = entry.getKey();
            if (!visited.contains(next)) {
                double result = dfs(graph, next, target, value * entry.getValue(), visited);
                if (result != -1.0) {
                    return result;
                }
            }
        }
        
        // 如果没有找到路径,返回-1.0
        return -1.0;
    }
    
    private Map<String, Map<String, Double>> buildGraph(List<List<String>> equations, double[] values) {
        Map<String, Map<String, Double>> graph = new HashMap<>();
        
        for (int i = 0; i < equations.size(); i++) {
            List<String> equation = equations.get(i);
            String var1 = equation.get(0);
            String var2 = equation.get(1);
            double value = values[i];
            
            graph.putIfAbsent(var1, new HashMap<>());
            graph.putIfAbsent(var2, new HashMap<>());
            
            graph.get(var1).put(var2, value);
            graph.get(var2).put(var1, 1 / value);
        }
        
        return graph;
    }
}

该代码首先构建了一个图,然后对每个查询使用深度优先搜索来寻找路径,并计算路径上所有边的权重乘积。如果在图中找到了对应的路径,则返回计算结果;否则,返回-1.0。

四、时间复杂度和空间复杂度

1. 时间复杂度
  • 构建图的时间复杂度:对于每个等式,我们需要在图中添加两个节点和它们之间的两条边。由于每个等式需要常数时间操作,构建图的时间复杂度为 O(E),其中 E 是等式的数量。

  • 查询处理的时间复杂度:对于每个查询,我们使用深度优先搜索(DFS)来寻找从起点到终点的路径。在最坏的情况下,DFS 可能会遍历图中的所有节点。如果图是完全连通的,则每个查询的时间复杂度为 O(V),其中 V 是图中节点的数量。由于我们需要对每个查询都执行一次 DFS,因此总的时间复杂度为 O(Q * V),其中 Q 是查询的数量。

  • 综合时间复杂度:构建图的时间复杂度为 O(E),查询处理的时间复杂度为 O(Q * V)。如果每个变量都至少出现在一个等式中,则 V 的数量最多为 2E(因为每个等式有两个变量),因此 V 可以认为是 O(E)。所以,总的时间复杂度为 O(E + Q * E) = O((E + Q) * E)。

2. 空间复杂度
  • 图的空间复杂度:图由节点和边组成,每个节点关联一个映射,映射中的每个键值对代表一条边。因此,图的空间复杂度为 O(V + E),其中 V 是节点数量,E 是边数量。

  • DFS 的空间复杂度:在 DFS 过程中,我们使用了一个集合来记录访问过的节点,这个集合的空间复杂度为 O(V)。此外,DFS 的递归调用栈的深度最多为 V,因此递归栈的空间复杂度也是 O(V)。

  • 结果数组的空间复杂度:结果数组的大小与查询的数量 Q 成正比,因此空间复杂度为 O(Q)。

  • 综合空间复杂度:图的空间复杂度为 O(V + E),DFS 的空间复杂度为 O(V),结果数组的空间复杂度为 O(Q)。因为 V 可以认为是 O(E),所以总的空间复杂度为 O(E + Q)。

五、总结知识点

  • 数据结构

    • List:用于存储等式和查询。
    • Map:用于构建图,其中键是变量名,值是另一个映射,映射的键是相邻节点,值是边的权重。
    • Set:用于在深度优先搜索(DFS)中记录已访问的节点,避免重复访问。
  • 算法

    • 图的构建:通过遍历等式和值数组,将每个等式转换为图中的一条边。
    • 深度优先搜索(DFS):用于在图中寻找从起点到终点的路径,并计算路径上所有边的权重乘积。
  • Java编程

    • 泛型:在集合中使用泛型来保证类型安全。
    • 方法重载putIfAbsent 方法在 Map 中用于添加键值对,如果键不存在则添加,否则不添加。
    • 递归:在 dfs 方法中使用递归来实现深度优先搜索。
    • 异常处理:通过检查 Map 中是否包含特定的键来避免 NullPointerException
  • 逻辑控制

    • 条件语句:使用 if-else 语句来处理不同的情况,例如检查查询中的变量是否存在于图中。
    • 循环:使用 for 循环来遍历等式、查询和图的邻接节点。
  • 数学概念

    • 分数和比例:代码的核心是处理变量之间的比例关系,即等式中的除法操作。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。


原文地址:https://blog.csdn.net/weixin_62860386/article/details/143810391

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!