面试经典 150 题 4 —(图)— 399. 除法求值

399. 除法求值

在这里插入图片描述

方法:

  将整个问题建模成一张图:给定图中的一些点(变量),以及某些边的权值(两个变量的比值),试对任意两点(两个变量)求出其路径长(两个变量的比值)。

class Solution {
public:
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        // 1.题目中给出:“如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案”,所以需要先从给出的变量对数组中整理出所有存在的不同的变量(字符串),相当于整理图的节点
        // variables存储条件(变量对数组)中给出的变量
        unordered_set<string> variables;
        // 遍历方程组,建立变量与索引的映射关系
        for(int i = 0; i < equations.size(); i++){
            // 题目中给出“每个 Ai 或 Bi 是一个表示单个变量的字符串。”,说明变量对数组中的每个变量对中,可能只有一个元素,所以下边应该分别判断equations[i][0] 和 equations[i][1]
            // 如果set中没有这个字符串,就加进去,使用set中不能有有重复元素的特性,能够方便地整理条件中的变量
            if(!variables.count(equations[i][0])){
                variables.emplace(equations[i][0]);
            }
            if(!variables.count(equations[i][1])){
                variables.emplace(equations[i][1]);
            }
        }
        // 2. 完善图中的节点邻居信息和路径的权值信息
        // 对于每个点(也就是字符串代表的变量),存储与其直接相连的所有节点以及对应的权值;第一个string就是当前的节点,vector<pair<string, double>>>中的string就是邻居节点,double就是对应二者的商值
        unordered_map<string, vector<pair<string, double>>> edges;
        for(int i = 0; i < equations.size(); i++){
            string Ai = equations[i][0];
            string Bi = equations[i][1];
            // 添加双向边,权值为给定的商值和其倒数
            edges[Ai].push_back(make_pair(Bi, values[i]));
            edges[Bi].push_back(make_pair(Ai, 1.0 / values[i]));
        }

        // 3. 使用BFS计算queries:过程相当于暂时更新所有从Cj出发的节点的值,更新方式就是用前一段路径的权值✖后一段路径的权值,因为是从Cj开始,所以广度优先遍历完,就相当于记录了所有Cj除以其他节点的结果,放到ratios数组中。
        // 存储queries中每一对的计算结果
        vector<double> queryResults;
        // 遍历问题数组,计算结果
        for (const auto& query: queries) {
            // 如果无法确定答案或者出现了条件中没有的字符串,都返回-1,所以将result初始化为-1
            double result = -1.0;
            // 如果要计算的query的两个变量都是已有的变量(因为set中不能有重复的元素,所以count的结果=1就表示有这个元素,=0就表示没有这个元素,不可能再出现其他的数值,这样就能判断set中是否有这个元素)
            if (variables.count(query[0]) && variables.count(query[1])) {
                // Cj和Dj
                string Cj = query[0], Dj = query[1];
                // 如果是同一个变量,商值为1
                if (Cj == Dj) {
                    result = 1.0;
                } 
                else { // 就说明两个变量不相同,而且二者在图中不一定相邻,所以就需要使用广度优先搜索计算变量之间的商值
                    // ratios数组用于暂时保存从Cj遍历到其他所有节点的过程中计算的值。
                    unordered_map<string, double> ratios;
                    // Cj是开始的地方,所以赋值为1即可
                    ratios[Cj] = 1.0;
                    // 广度优先遍历,使用队列辅助;
                    queue<string> points;
                    // 先把相当于根节点的Cj入队
                    points.push(Cj);
                    // 遍历图中的节点,更新变量之间的关系;
                    // 如果map中有了Dj,就说明遍历过程中,已经计算到了Dj,就能退出循环了
                    // 还有可能没有能够到达Dj的路径,就是正常的BFS结束
                    while (!points.empty() && !ratios.count(Dj)) {
                        // 广度优先搜索的流程
                        string x = points.front();
                        points.pop();
                        // 遍历与当前变量直接相连的变量
                        for (const auto [y, val]: edges[x]) {
                            // 表示还没计算到当前节点
                            if (!ratios.count(y)) {
                                // 更新Cj除以当前节点变量的商值,并将下一个变量加入队列
                                ratios[y] = ratios[x] * val;
                                points.push(y);
                            }
                        }
                    }
                    // 如果是找到了Dj,那么ratios[Dj]中就是保存的从Cj一路计算,直到找到Dj时计算的Cj/Dj的结果
                    if(ratios.count(Dj)){
                        result = ratios[Dj];
                    }
                }
            }
            // 将计算结果添加到返回向量
            queryResults.push_back(result);
        }
        // 返回queries中每一对的计算结果
        return queryResults;
    }
};
class Solution {
public:
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        // 1.题目中给出:“如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案”,所以需要先从给出的变量对数组中整理出所有存在的不同的变量(字符串),相当于整理图的节点
        // 变量对的对数,用于循环遍历变量对数组
        int equationsSize = equations.size();
        // variables存储变量与索引的映射关系,通过哈希表将每个不同的字符串映射成整数。
        unordered_map<string, int> variables;
        // 变量的索引,用于后续方便查找
        int nvars = 0;
        // 遍历方程组,建立变量与索引的映射关系
        for(int i = 0; i < equationsSize; i++){
            // 题目中给出“每个 Ai 或 Bi 是一个表示单个变量的字符串。”,说明变量对数组中的每个变量对中,可能只有一个元素,所以下边应该分别判断equations[i][0] 和 equations[i][1],将其记录为存在的变量,并映射为整数;
            if(variables.find(equations[i][0]) == variables.end()){
                variables[equations[i][0]] = nvars++;
            }
            if(variables.find(equations[i][1]) == variables.end()){
                variables[equations[i][1]] = nvars++;
            }
        }
        // 2. 完善图中的节点邻居信息和路径的权值信息
        // 对于每个点,存储与其直接相连的所有节点以及对应的权值;这里就能直接用索引值表示节点了
        vector<vector<pair<int, double>>> edges(nvars);
        for(int i = 0; i < equationsSize; i++){
            int Ai = variables[equations[i][0]];
            int Bi = variables[equations[i][1]];
            // 添加双向边,权值为给定的商值和其倒数
            edges[Ai].push_back(make_pair(Bi, values[i]));
            edges[Bi].push_back(make_pair(Ai, 1.0 / values[i]));
        }

        // 3. 使用BFS计算queries:过程相当于暂时更新所有从Cj出发的节点的值,更新方式就是用前一段路径的权值✖后一段路径的权值,因为是从Cj开始,所以广度优先遍历完,就相当于记录了所有Cj除以其他节点的结果,放到ratios数组中。
        // 存储queries中每一对的计算结果
        vector<double> queryResults;
        // 遍历问题数组,计算结果
        for (const auto& query: queries) {
            // 如果无法确定答案或者出现了条件中没有的字符串,都返回-1,所以将result初始化为-1
            double result = -1.0;
            // 如果要计算的query的两个变量都在映射表中
            if (variables.find(query[0]) != variables.end() && variables.find(query[1]) != variables.end()) {
                // query_Cj和query_Dj相当于query中的变量的索引
                int query_Cj = variables[query[0]], query_Dj = variables[query[1]];
                // 如果是同一个变量,商值为1
                if (query_Cj == query_Dj) {
                    result = 1.0;
                } 
                else { // 就说明两个变量不相同,而且二者在图中不一定相邻,所以就需要使用广度优先搜索计算变量之间的商值
                    // ratios数组用于暂时保存从Cj遍历到其他所有节点的过程中计算的值。
                    vector<double> ratios(nvars, -1.0);
                    // query_Cj是开始的地方,所以赋值为1即可
                    ratios[query_Cj] = 1.0;
                    // 广度优先遍历,使用队列辅助;
                    queue<int> points;
                    // 先把相当于根节点的query_Cj入队
                    points.push(query_Cj);
                    // 遍历图中的节点,更新变量之间的关系;如果ratios[query_Dj] >= 0了,就说明遍历过程中,已经计算到了Dj,就可以退出了
                    while (!points.empty() && ratios[query_Dj] < 0) {
                        // 广度优先搜索的流程
                        int x = points.front();
                        points.pop();
                        // 遍历与当前变量直接相连的变量
                        for (const auto [y, val]: edges[x]) {
                            // 表示还没计算到当前节点
                            if (ratios[y] < 0) {
                                // 更新Cj除以当前节点变量的商值,并将下一个变量加入队列
                                ratios[y] = ratios[x] * val;
                                points.push(y);
                            }
                        }
                    }
                    // ratios[query_Dj]中就保存了从Cj一路计算,直到找到Dj时计算的Cj/Dj的结果
                    result = ratios[query_Dj];
                }
            }
            // 将计算结果添加到返回向量
            queryResults.push_back(result);
        }
        // 返回queries中每一对的计算结果
        return queryResults;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值