LeetCode 399. Evaluate Division

Graph+BFS/DFS

这道题最好想到的就是建立一个graph,两个节点的ratio就当做图的边保存下来。

query的时候,只要从一个节点开始,dfs或者bfs,把一路的ratio都乘起来即可。

用BFS来做:

class Solution {
public:
    vector<double> calcEquation(vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries) {
        unordered_map<string, unordered_map<string, double>> graph;
        for (int i=0;i<equations.size();++i){
            string a=equations[i].first, b=equations[i].second;
            graph[a][b] = values[i];    
            graph[b][a] = 1.0/values[i];
            graph[a][a] = 1.0;
            graph[b][b] = 1.0;
        }
        
        vector<double> res;
        for (auto query:queries){
            string a=query.first, b=query.second;
            if (!graph.count(a) || !graph.count(b)){
                res.push_back(-1.0);
                continue;
            }
            queue<pair<string,double>> q; // <curNode, product so far>
            unordered_set<string> used({a});
            bool find=false;
            q.push({a,1.0});
            while (!q.empty()){
                auto cur=q.front(); q.pop();
                if (cur.first==b){
                    res.push_back(cur.second);
                    find = true;
                    break;
                }
                for (pair<string,double> x:graph[cur.first]){
                    if (!used.count(x.first)){
                        used.insert(x.first);
                        x.second *= cur.second;
                        q.push(x);
                    }
                }
            }
            if (!find) res.push_back(-1.0);   
        }
        return res;
    }
};

 

Union Find

用图来做,既需要大量空间,而且dfs和bfs的效率都不是很高。

这道题也可以用Union Find来做,有关联的字符串就union在一起。核心是union的时候,相对的大小都是正确的。

对于a/b=v,如果都没创建过节点,那么a=v, b=1即可。如果一个节点已经存在,那么我们计算出相应另一个节点的value,并把已存在节点作为新节点的parent,否则已存在节点有两个parent是不行的。

本题的关键是,当两个节点都已经存在时,这两个节点可能在不同的set里。为了保证 node1->val / node2->val = values[i],我们是要对一边的集合进行改动的。

如果我们要对 node1 所在的set里所有元素进行缩放,这个比例可以很简单计算出来:

double ratio = value * node2->val / node1->val; 谁作为谁的root无所谓。

相比graph,这种方法query很方便。如果不在一个set中,直接返回-1。在的话,直接两个节点的val相除即可,因为所有节点的相对关系都是正确的。

class Solution {
public:
    vector<double> calcEquation(vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries) {
        vector<double> res;
        for (int i=0;i<equations.size();++i){
            string s1=equations[i].first, s2=equations[i].second;
            if (!m.count(s1) && !m.count(s2)){
                m[s1] = new Node(values[i]);
                m[s2] = new Node(1.0);
                m[s1]->parent = m[s2];
            }else if (!m.count(s1)){ // s2 exists
                m[s1] = new Node(m[s2]->val*values[i]);
                m[s1]->parent = m[s2];
            }else if (!m.count(s2)){ // s1 exists
                m[s2] = new Node(m[s1]->val/values[i]);
                m[s2]->parent = m[s1];
            }else{
                unionSet(s1,s2,values[i]);
            }
        }
        for (auto query:queries){
            string s1=query.first, s2=query.second;
            if (!m.count(s1) || !m.count(s2) || findSet(m[s1])!=findSet(m[s2]))
                res.push_back(-1.0);
            else{
                res.push_back( m[s1]->val / m[s2]->val );
            }
        }
        return res;
    }
    
private:
    struct Node{ // for convenience, rank is not included
        double val;
        Node *parent;
        Node(double v):val(v),parent(this){}
    };
    
    unordered_map<string, Node *> m; //node->val to Node *
    
    void unionSet(string s1, string s2, double value){
        Node *node1=m[s1], *node2=m[s2];
        Node *root1=findSet(node1), *root2=findSet(node2);
        double ratio = value * node2->val / node1->val;
        for (auto it=m.begin();it!=m.end();++it)
            if (findSet(it->second)==root1)
                it->second->val *= ratio;
        root1->parent = root2;
    }
    
    Node *findSet(Node *node){
        if (node->parent==node) return node;
        node->parent = findSet(node->parent);
        return node->parent;
    }
    
};

 

Floyd-Warshall Algorithm

本题需要query多次,很容易联想到 Floyd-Warshall Algorithm 求解 all pair shortest path 的问题。由于本题无需考虑最短路的问题,及时有多条路答案应该也是一样的,因此我们不需要 if 的判断。此外,由于本题是做乘法,dist[i][i] 应该初始化为1而不是0。还有需要注意的是,枚举i, j, k的时候,j和k必须要是i的邻居才行,否则 dist[i][k] 和 distp[k][j] 是不存在的 (unordered_map访问不存在的元素会新建并初始化为0由于是double),会导致最后的结果都是0。

最后query的时候也要保住 dist[from][to] 是存在的。

class Solution {
public:
    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        unordered_map<string, unordered_map<string, double>> graph;
        for (int i=0;i<equations.size();++i){
            string a=equations[i][0], b=equations[i][1];
            graph[a][b] = values[i];    
            graph[b][a] = 1.0/values[i];
            graph[a][a] = graph[b][b] = 1.0;
        }
        
        unordered_map<string, unordered_map<string, double>> dist(graph);
        
        for (auto &x:dist){
            string k=x.first;
            for (auto &y:dist[k]){
                string i=y.first;
                for (auto &z:dist[k]){
                    string j=z.first;
                    dist[i][j] = dist[i][k] * dist[k][j];
                }
            }
        }

        vector<double> res;
        for (auto query:queries){
            string from=query[0], to=query[1];
            if (dist.count(from) && dist[from].count(to))
                res.push_back(dist[from][to]);
            else res.push_back(-1.0);  
        }
        return res;
    }
};

时间复杂度 O(V^3)

 

References:

https://leetcode.com/problems/evaluate-division/discuss/88170/0ms-C++-Union-Find-Solution-EASY-to-UNDERSTAND

http://www.cnblogs.com/grandyang/p/5880133.html

https://leetcode.com/problems/evaluate-division/discuss/88175/9-lines-%22FloydWarshall%22-in-Python

posted @ 2018-11-13 03:28  約束の空  阅读(591)  评论(0)    收藏  举报