【面试经典 150 | 图】除法求值

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【图】【广度优先搜索】


题目来源

399. 除法求值


解题思路

方法一:广度优先搜索

思路

我们可以将整个问题建模成一张图:给定图中的一些点(变量),以及某些边的权值(两个变量之间的比值),试着求出任意两点(变量)之间的路径长(两个变量的比值).

因此,我们首先需要遍历 equations 数组,找出其中所有不同的字符串,并通过哈希表将每个不同的字符串映射成整数。具体地,使用 整型数字 表示字符串,不同的整型数字代表不同的字符串。variables 即为字符串到整型数字的映射哈希表。

接着建图,利用字符串数组 equations 和 浮点双精度数组 values 建立无向图。

在构建完图之后,对于任何一个查询,就可以从起点出发,通过广度优先搜索的方式,不断更新起点与当前点之间的路径长度,直到搜索到终点为止。具体地,查询步骤如下:

  • 遍历每一个查询,利用哈希表找出查询的字符串的整型映射值;
  • 如果整型映射值 ia = ib,则更新这两个字符串之间的权值结果 result = 1.0
  • 否则通过广度优先搜索计算权值:
    • 维护一个队列存放整型数值,初始化加入 ia;维护一个数组 rationsrations[y] 表示从 iay 之间的权值,默认初始化数组元素都为 -1,更新 rations[ia] = 1.0
    • 只要队列非空,并且 rations[ib] < 0(还么找到权值关系),则继续迭代;
    • 利用当前出队元素的边,广搜更新下去;
    • 最后更新 result = rations[ib],如果没有找到 iaib 之间的权值关系(不存在),则 ration[ib] 就是初始化的值 -1
  • 将当前查询的结果放入答案数组 ret 中;
  • 最后返回答案数组。

代码

class Solution {
public:
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        int nvars = 0;
        unordered_map<string, int> variables;
        int n = equations.size();
        for (int i = 0; i < n; ++i) {
            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++;
            }
        }

        //对于每个点,储存其直接连接到所有点及其对应的权值
        vector<vector<pair<int, double>>> edges(nvars);
        for (int i = 0; i <n; ++i) {
            int va = variables[equations[i][0]], vb = variables[equations[i][1]];
            edges[va].push_back(make_pair(vb, values[i]));
            edges[vb].push_back(make_pair(va, 1.0 / values[i]));
        }

        vector<double> ret;
        for (const auto& q : queries) {
            double result = -1.0;
            if (variables.find(q[0]) != variables.end() && variables.find(q[1]) != variables.end()) {
                int ia = variables[q[0]], ib = variables[q[1]];
                if (ia == ib) {
                    result = 1.0;
                }
                else {
                    queue<int> points;
                    points.push(ia);
                    vector<double> rations(nvars, -1.0);
                    rations[ia] = 1.0;

                    while (!points.empty() && rations[ib] < 0) {
                        int x = points.front(); points.pop();

                        for (const auto [y, val] : edges[x]) {
                            if (rations[y] < 0) {
                                rations[y] = rations[x] * val;
                                points.push(y);
                            }
                        }
                    }
                    result = rations[ib];
                }
            }
            ret.push_back(result);
        }
        return ret;
    }
};

复杂度分析

时间复杂度: O ( M L + Q ⋅ ( L + M ) ) O(ML+Q\cdot(L+M)) O(ML+Q(L+M)),其中 M M M 为边的数量, Q Q Q 为询问的数量, L L L 为字符串的平均长度。构建图时,需要处理 M M M 条边,每条边都涉及到 O ( L ) O(L) O(L) 的字符串比较;处理查询时,每次查询首先要进行一次 O ( L ) O(L) O(L) 的比较,然后至多遍历 O ( M ) O(M) O(M) 条边。

空间复杂度: O ( N L + M ) O(NL+M) O(NL+M),其中 N N N 为点的数量, M M M 为边的数量, L L L 为字符串的平均长度。为了将每个字符串映射到整数,需要开辟空间为 O ( N L ) O(NL) O(NL) 的哈希表;随后,需要花费 O ( M ) O(M) O(M) 的空间存储每条边的权重;处理查询时,还需要 O ( N ) O(N) O(N) 的空间维护访问队列。最终,总的复杂度为 O ( N L + M + N ) = O ( N L + M ) O(NL+M+N)=O(NL+M) O(NL+M+N)=O(NL+M)


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wang_nn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值