LeetCode 399.除法求值 并查集算法详细解析

题目链接

给出方程式 A / B = k, 其中 A 和 B 均为代表字符串的变量, k是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。

示例 : 给定 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 ]

基于上述例子,输入如下:

equations(方程式) = [ [“a”, “b”], [“b”, “c”] ],
values(方程式结果) = [2.0,3.0],
queries(问题方程式) = [ [“a”, “c”], [“b”, “a”], [“a”, “e”], [“a”, “a”], [“x”, “x”] ].

输入总是有效的。你可以假设除法运算中不会出现除数为0的情况,且不存在任何矛盾的结果。

并查集求解思路:

数学思想:将题目中给出的方程式换成连比的形式,如:a/b=2.0, b/c=3.0, 则 a: b : c=2:1:(1/3),因此很容易得出a/c=2/(1/3)=6.

编程思想:问题的关键是用怎样的数据结构来表示这个连比关系,一种比较好的办法是用 树,如:a/b=2.0, b/c=3.0,可以建树为:
在这里插入图片描述整棵树存储到map中,map中的每一项存储树的结点名、该结点对应的数值以及该结点的父结点,如下图所示:
在这里插入图片描述

  1. 建树过程
    这里举一个包括所有情况的例子:
    equations(方程式) = [ [“a”, “b”], [“b”, “c”], [“e”,“c”], [“f”,“g”], [“e”,“g”] ],
    values(方程式结果) = [2.0, 3.0, 2.0, 2.0, 3.0],
    1)a/b=2.0:a和b都不在树中,则将a结点的值设为2.0,b结点的值设为1.0,a为b的父结点(a、b谁做父结点都一样,因为同一棵树中的所有结点表示一种连比关系),然后将两个结点存入map中;
    2) b/c=3.0:c不在树中,若要跟树中的a, b形成连比关系,就要根据b的值来确定c的值(显然c的值为b/3.0),同时b作为c的父结点,然后将c插入树中;
    3)e/c=2.0:e不在树中,若要跟树中的a, b, c构成连比关系,就要根据c的值来确定e的值(显然e的值为c*2.0), 同时c作为e的父结点,然后将e插入树中;
    4)f/g=2.0:和1)同理,f的值设为2.0,g的值设为1.0,f为g的父结点,插入树中;
    5)e/g=3.0:e和g分属于两棵树,这种情况比较复杂,直接上图:

    在这里插入图片描述
    在这里,我们需要根据e/g=3.0来将树①和树②合并成一棵树,也就是将两个连比关系合并成一个。不妨保持树②不动,按照相应的比例改变树①各值,然后合并。所以现在的重中之重是求出这个比例,既然树②各值不变,那么根据关系式e/g=3.0可以得出e的值应该变为g乘以3.0,由此e结点的值扩大(或者缩小)的倍数为g乘以3.0/(2/3),因此树①中每个结点的值都应该扩大(或者缩小)这个倍数。如下图:
    在这里插入图片描述建树完成!

  2. 计算
    根据这棵树便可以计算树中任意两结点之间的比值,特殊情况:若有结点不在树中,直接设置其结果为-1.0。

  3. 代码c++

class Solution {
private:
   struct Node{
       double val;
       Node* parent;
       Node():parent(this){}
       Node(double v): val(v),parent(this){}
   };

public:
   Node* find_father(Node* n){
       if (n->parent == n)
           return n;
       else
           return find_father(n->parent);
   }

   void merge(Node* n1, Node* n2, double value, 
   					unordered_map<string,Node*>& m){
       Node* p1=find_father(n1), *p2=find_father(n2);
       double ratio=value*n2->val/n1->val;
       for(auto n : m){
           if(find_father(n.second) == p1)
               n.second->val*=ratio;
       }
       p1->parent = p2;
   }

   vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
       vector<double> res;
       unordered_map<string, Node*> m;
       for ( int i = 0; i < equations.size(); ++i ) {
           string a = equations[i][0], b=equations[i][1];
           if(m.find(a) == m.end() && m.find(b) == m.end()){
               m[a] = new Node(values[i]);
               m[b] = new Node(1.0);
               m[b]->parent = m[a];
           } else if (m.find(a) == m.end()) {
               m[a] = new Node(values[i]*m[b]->val);
               m[a]->parent = m[b];
           } else if(m.find(b) == m.end()) {
               m[b] = new Node(m[a]->val/values[i]);
               m[b]->parent = m[a];
           } else {
               merge(m[a], m[b], values[i], m);
           }
       }

       for (int i = 0; i < queries.size(); ++i) {
           if (m.find(queries[i][0]) != m.end() && m.find(queries[i][1]) != m.end() && find_father(m[queries[i][0]]) == find_father(m[queries[i][1]])){
               res.push_back(m[queries[i][0]]->val/m[queries[i][1]]->val);
           } else {
               res.push_back(-1.0);
           }
       }
       return res;
   }
};
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值