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 替代这个答案。

注意:输入总是有效的。你可以假设除法运算中不会出现除数为 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 ]
示例 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 由小写英文字母与数字组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/evaluate-division/

特别说明

求解中采用了三种方法方法一、二采用广度优先搜索,只是两种实现中图的存储方式不一样,方法一种采用邻接矩阵存储,方法二中采用邻接表存储; 方法三采用flyod算法,不管哪种方法都是图中的求两个点间的最短路径办法,也就是图中可能存在多条路径,那值就可能不一致,为什么可以用来解决这个问题???请特别注意题的描述:“输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果”,不存在任何矛盾的结果!!!那肯定在两点间要么不存在路径,要么存在也仅仅只有一条,当然就可以采用如上的方法求解。

求解

    class Solution {
    private:
        const double INVALID = -1.0;

        // 方法一辅助函数
        // 对图graph从顶点 v开始进行广度优先遍历,直到遇到w结束或者直到遍历完成
        // 注意,这儿是从某个顶点开始广度优先遍历,并非对整个图进行广度遍历
        void bfsTraverse(vector<vector<double>> &graph, int a, int b) {
            const int N = graph.size();
            // 广度优先遍历并更新权值
            vector<bool> visited(N, false);
            queue<int> q;
            q.push(a);
            visited[a] = true;
            // 一旦遍历到b点,立即结束遍历
            while (!q.empty() && !visited[b]) {
                int v = q.front();
                q.pop();
                // 遍历v邻接点
                for (int j = 0; j < N; ++j) {
                    // 1)j未被遍历 2)j是v的邻接点 3)a到v可达,这样才可能a->v->j
                    if (graph[v][j] != INVALID && !visited[j] && graph[a][v] != INVALID) {
                        graph[a][j] = graph[a][v] * graph[v][j];
                        q.push(j);
                        visited[j] = true;
                    }
                }
            }
        }

    public:
        // 方法一,构造有向带权图实现,采用邻接矩阵存储
        // 缺点:1)存储采用邻接矩阵有点浪费
        // 2)每次为了遍历一个新的a -> b,拷贝了一份图的存储,目的是遍历过程中修改值,存储和效率上的瑕疵
        vector<double>
        calcEquation_1e(vector<vector<string>> &equations, vector<double> &values, vector<vector<string>> &queries) {
            if (equations.empty()) {
                return vector<double>();
            }
            int vIndex = 0; // 顶点初始编号
            unordered_map<string, int> vHashMap; // 顶点到数值的映射
            for (auto &vec : equations) {
                if (vHashMap.count(vec[0]) == 0) {
                    vHashMap.emplace(vec[0], vIndex++);
                }
                if (vHashMap.count(vec[1]) == 0) {
                    vHashMap.emplace(vec[1], vIndex++);
                }
            }

            // 用二维矩阵即邻接矩阵存储图的信息
            vector<vector<double>> graph(vIndex, vector<double>(vIndex, INVALID));
            for (int i = 0; i < vIndex; ++i) {
                graph[i][i] = 1.0;
            }
            for (int i = 0; i < equations.size(); ++i) {
                int v = vHashMap[equations[i][0]];  // 顶点v的数值映射
                int w = vHashMap[equations[i][1]];  // 顶点w的数值映射
                graph[v][w] = values[i];
                graph[w][v] = 1.0 / values[i];
            }

            vector<double> res;
            for (int i = 0; i < queries.size(); ++i) {
                if ((vHashMap.count(queries[i][0])) > 0 && (vHashMap.count(queries[i][1]) > 0)) {
                    // 待求得点均在已构建的图中
                    auto gcp = graph;
                    int a = vHashMap[queries[i][0]];
                    int b = vHashMap[queries[i][1]];
                    bfsTraverse(gcp, a, b);
                    res.push_back(gcp[a][b]);
                    continue;
                }
                // 待求点未知
                res.push_back(-1);
            }
            return res;
        }

        // 方法二,构造有向带权图实现,采用邻接表实现,且遍历中不再采用辅助函数,也不用额外拷贝图的存储
        vector<double>
        calcEquation_2e(vector<vector<string>> &equations, vector<double> &values, vector<vector<string>> &queries) {
            if (equations.empty()) {
                return vector<double>();
            }
            int vIndex = 0; // 顶点初始编号
            unordered_map<string, int> vHashMap; // 顶点到数值的映射
            for (auto &vec : equations) {
                if (vHashMap.count(vec[0]) == 0) {
                    vHashMap.emplace(vec[0], vIndex++);
                }
                if (vHashMap.count(vec[1]) == 0) {
                    vHashMap.emplace(vec[1], vIndex++);
                }
            }

            // 采用邻接表构造图
            vector<vector<std::pair<int, double>>> edges(vIndex);
            for (int i = 0; i < equations.size(); ++i) {
                int v = vHashMap[equations[i][0]];  // 顶点v的数值映射
                int w = vHashMap[equations[i][1]];  // 顶点w的数值映射
                edges[v].emplace_back(w, values[i]);
                edges[w].emplace_back(v, 1.0 / values[i]);
            }

            // 根据待求每对点进行广度优先遍历
            vector<double> res;
            for (int i = 0; i < queries.size(); ++i) {
                // 待求点均在已构建的图中
                if ((vHashMap.count(queries[i][0])) > 0 && (vHashMap.count(queries[i][1]) > 0)) {
                    int a = vHashMap[queries[i][0]];
                    int b = vHashMap[queries[i][1]];
                    // 待求点相等,无需遍历
                    if (a == b) {
                        res.push_back(1.0);
                        continue;
                    }
                    // 待求点不等,需要遍历
                    vector<double> ratios(vIndex, -1.0);
                    queue<int> q;
                    q.push(a);
                    ratios[a] = 1.0;
                    while (!q.empty() && (ratios[b] == -1.0)) {
                        int v = q.front();
                        q.pop();
                        // 关注下一行的C++新特性
                        for (const auto &[w, weight] : edges[v]) {
                            // 遍历v的所有邻接点
                            if (ratios[w] == -1.0) {
                                ratios[w] = ratios[v] * weight;
                                q.push(w);
                            }
                        }
                    }
                    res.push_back(ratios[b]);
                    continue;
                }
                // 待求点未知,即不在构建的图中
                res.push_back(-1);
            }
            return res;
        }

        // 方法三,构造有向带权图实现,采用邻接矩阵存储,使用floyd算法求出任意两点间的值(假如存在),而不用每次进行遍历查询
        vector<double>
        calcEquation(vector<vector<string>> &equations, vector<double> &values, vector<vector<string>> &queries) {
            if (equations.empty()) {
                return vector<double>();
            }
            int vIndex = 0; // 顶点初始编号
            unordered_map<string, int> vHashMap; // 顶点到数值的映射
            for (auto &vec : equations) {
                if (vHashMap.count(vec[0]) == 0) {
                    vHashMap.emplace(vec[0], vIndex++);
                }
                if (vHashMap.count(vec[1]) == 0) {
                    vHashMap.emplace(vec[1], vIndex++);
                }
            }

            // 用二维矩阵即邻接矩阵存储图的信息
            vector<vector<double>> graph(vIndex, vector<double>(vIndex, INVALID));
            for (int i = 0; i < vIndex; ++i) {
                graph[i][i] = 1.0;
            }
            for (int i = 0; i < equations.size(); ++i) {
                int v = vHashMap[equations[i][0]];  // 顶点v的数值映射
                int w = vHashMap[equations[i][1]];  // 顶点w的数值映射
                graph[v][w] = values[i];
                graph[w][v] = 1.0 / values[i];
            }

            // floyd算法遍历,求出任意两点间的值
            for (int i = 0; i < vIndex; ++i) {
                for (int v = 0; v < vIndex; ++v) {
                    for (int w = 0; w < vIndex; ++w) {
                        if (graph[v][i] != INVALID && graph[i][w] != INVALID) {
                            graph[v][w] = graph[v][i] * graph[i][w];
                        }
                    }
                }
            }

            // 通过表查询待求点对的值
            vector<double> res;
            for (int i = 0; i < queries.size(); ++i) {
                // 待求点均在已构建的图中
                if ((vHashMap.count(queries[i][0])) > 0 && (vHashMap.count(queries[i][1]) > 0)) {
                    int a = vHashMap[queries[i][0]];
                    int b = vHashMap[queries[i][1]];
                    res.push_back(graph[a][b]);
                    continue;
                }
                // 待求点未知,即不在构建的图中
                res.push_back(-1);
            }
            return res;
        }
    };

猜你喜欢

转载自blog.csdn.net/u010323563/article/details/112285984
今日推荐