LeetCode每日一题--399. 除法求值(广度优先搜索 动态规划 带权并查集)

题目:跳转至 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 ]

示例 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 由小写英文字母与数字组成
class Solution {
public:
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {

    }
};

思路:
思路可以有,把 equations 变量对中存在相同变量的值统一管理,如 [[“a”,“b”],[“b”,“c”]] 中的 “a”,“b”,“c” 都可以用 “a” 来表示,但就是写不出来,学习题解。

把问题建模成图,各个变量就是点,变量之间的比值就是边的权值,问题就成了求任意两点间的路径长。

方法一:广度优先搜索

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){ //遍历equations中出现过的值,通过哈希表把nvars的值赋给他们(即把字符串映射成整数)
            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]];
            int vb=variables[equations[i][1]];
            edges[va].push_back(make_pair(vb,values[i]));  //edges[va]记录对应vb及va/vb的值,即记录边va->vb及其权值
            edges[vb].push_back(make_pair(va,1.0/values[i]));  //edges[vb]记录va及vb/va的值,即记录边vb->va及其权值
        }

        vector<double> ret;
        for(const auto& q:queries){
            double result=-1.0; //初始化每个值为无解状态 -1.0
            if(variables.find(q[0])!=variables.end() && variables.find(q[1])!=variables.end()){  //两个值都找到肯定能解
                int ia=variables[q[0]];  //把字符串变为其对应的整数
                int ib=variables[q[1]];
                if(ia==ib)
                    result=1.0;
                else{
                    queue<int> points;
                    points.push(ia);  //从起点出发,先把当前除数放入队列中
                    vector<double> ratios(nvars,-1.0);  //初始化ia/ib比例个数为所有出现字符串的总数,且都无解
                    ratios[ia]=1.0;  //对应放入自身比例,ia/ia=1.0
                    while(!points.empty() && ratios[ib]<0){  //如果队列中有值且ia/ib的比例一直没找到
                        int x=points.front();  //获取队首的点
                        points.pop();
                        for(const auto [y,val]:edges[x]){  //遍历点对应的边集
                            if(ratios[y]<0){  //如果x/y比例未更新
                                ratios[y]=ratios[x]*val;  //相乘更新比例(遍历edges[ib]时,ratios(ib)=(ia/ia)*(ia/ib),则ratios(y)=(ia/ia)*(ia/ib)*(ib/y))
                                points.push(y);  //放入点y
                            }
                        }
                    }
                    result=ratios[ib];
                }
            }
            ret.push_back(result);
        }
        return ret;
    }
};

方法二:Floyd 算法

如果查询数量很多,每次查询都独立搜索一次的效率会很低,可以预先计算出任意两点之间的距离,这样在查询时只要读取计算后的值就可以了。

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){ //遍历equations中出现过的值,把nvars的值赋给他们(即把字符串映射成整数)
            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<double>> graph(nvars,vector<double>(nvars,-1.0));  //初始化出一个nvaes*nvars大小的矩阵存储任两点间的距离
        for(int i=0;i<n;++i){
            int va=variables[equations[i][0]];
            int vb=variables[equations[i][1]];
            graph[va][vb]=values[i];  //记录边va->vb及其权值
            graph[vb][va]=1.0/values[i];  //记录边vb->va及其权值
        }
        for(int k=0;k<nvars;++k){
            for(int i=0;i<nvars;++i){
                for(int j=0;j<nvars;++j){
                    if(graph[i][k]>0 && graph[k][j]>0)  //如果存在计算关系
                        graph[i][j]=graph[i][k]*graph[k][j];  //计算任两点间的比例,a/b * b/c=a/c
                }
            }
        }

        vector<double> ret;
        for(const auto& q:queries){
            double result=-1.0; //初始化每个值为无解状态 -1.0
            if(variables.find(q[0])!=variables.end() && variables.find(q[1])!=variables.end()){  //两个值都找到肯定能解
                int ia=variables[q[0]];  //把字符串变为其对应的整数
                int ib=variables[q[1]];
                if(graph[ia][ib]>0)
                    result=graph[ia][ib];
            }
            ret.push_back(result);
        }
        return ret;
    }
};

方法三:带权并查集

class Solution {
public:
    int findf(vector<int>& f, vector<double>& w, int x) {  //寻找根节点
        if (f[x] != x) {  //如果自己的根节点不是自己
            int father = findf(f, w, f[x]);  //递归寻找,x的根节点是f[x],...,直至f[x]==f[x],找到最终的根节点
            w[x] = w[x] * w[f[x]];  //x的权值更新为与根节点权值的乘积,如a/b=2,b/c=3,那么a/c=a/b * b/c
            f[x] = father;  //记录x的根节点
        }
        return f[x];
    }

    void merge(vector<int>& f, vector<double>& w, int x, int y, double val) {  //合并两个集合
        int fx = findf(f, w, x);  //寻找各自根节点
        int fy = findf(f, w, y);
        if(fx!=fy){  //如果根节点不同
            f[fx] = fy;  //合并把x的根节点放在y的根节点下,即y的根节点是x的根节点
            w[fx] = val * w[y] / w[x]; //更新x根节点的权值
            //如果val为a/d=6(a的根节点为d),已知现在a/b=3(w[a]=3,根节点b),d/c=4(w[d]=4,根节点c),
            //合并a,d所在集合,把a的根节点b放在d的根节点c下,w[b](根节点c,即求b/c)=a/d * d/c * b/a=a/d * d/c / (a/b)=val*w[d]/w[a]
        }
    }

    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){ //遍历equations中出现过的值,通过哈希表把nvars的值赋给他们(即把字符串映射成整数)
            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<int> f(nvars);
        vector<double> w(nvars,1.0);  //自己为自己的根节点时,权值为1
        for(int i=0;i<nvars;++i)
            f[i]=i;  //初始化自己为自己的根节点
        for(int i=0;i<n;++i){
            int va=variables[equations[i][0]];
            int vb=variables[equations[i][1]];
            merge(f,w,va,vb,values[i]);  //合并两个节点
        }

        vector<double> ret;
        for(const auto& q:queries){
            double result=-1.0; //初始化每个值为无解状态 -1.0
            if(variables.find(q[0])!=variables.end() && variables.find(q[1])!=variables.end()){  //两个值都找到肯定能解
                int ia=variables[q[0]];  //把字符串变为其对应的整数
                int ib=variables[q[1]];
                int fa=findf(f,w,ia);  //寻找对应的根节点
                int fb=findf(f,w,ib);
                if(fa==fb)  //如果根节点相同直接求出比值
                    result=w[ia]/w[ib];
            }
            ret.push_back(result);
        }
        return ret;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值