← Home

399. Evaluate Division

LeetCode article · C++ solution
Website made by wuisabel-gif · Original C++ code by keineahnung2345
union-findC++Markdown
399

The trick here is to name the state correctly, then let the implementation follow. For 399. Evaluate Division, the solution in this repository is mainly a union-find solution.

Guide

What?

Before optimizing anything, pin down what information is still useful after each move. Instead of trying to be clever immediately, read the code as a sequence of questions:

  • What state are we keeping?
  • How do we move from one state to the next?
  • When do we know the answer is already determined?

For this file, the main tools are: union-find, graph traversal.

The notes already sitting in the source point us in the right direction:

  • union find
  • WA
  • [["a","c"],["b","e"],["c","d"],["e","d"]]
  • [2.0,3.0,0.5,5.0]

Guide

When?

Reach for this shape when a direct simulation would work logically but waste time revisiting the same information. The accepted code reduces that pressure by storing exactly the information that remains useful later.

The important function names to track are unite, calcEquation, oneGroup, dfs.

Guide

Why?

The point of the implementation is not to make the code longer. It is to avoid doing the same thinking twice.

  • A map keeps the lookup side cheap; the code pays a little memory to avoid repeated searching.
  • A set is doing the membership or uniqueness work, which keeps the main loop readable.
  • The queue gives the solution a level-by-level or frontier-style traversal.
  • The two-dimensional vector is the memory of the solution: grid state, DP state, or adjacency shape.

Guide

How?

Walk through the solution in this order:

  1. Read the setup variables first.
  2. Follow the main loop or recursive helper next.
  3. Watch where invalid states get skipped.
  4. Check which value survives to the return statement.

The most important competitive-programming habit here is to trust the invariant. Once the invariant is right, the loops become much less scary.

Guide

Complexity

  • Time: O(n) to O(n log n), depending on the dominant loop or data structure operation
  • Space: O(n) in the usual case for auxiliary containers or recursion

Guide

C++ Solution

Your submission

The accepted solution

solution.cpp
01//union find
02//WA
03//[["a","c"],["b","e"],["c","d"],["e","d"]]
04//[2.0,3.0,0.5,5.0]
05//[["a","b"]]
06//in the "connect (a,b) and (b,c) to (a,c)" part, it fails when the path is too long
07class DSU{
08public:
09    map<string, string> parent;
10    
11    DSU(set<string>& vertices){
12        for(string vertex : vertices){
13            parent[vertex] = vertex;
14        }
15    };
16    
17    string find(string x){
18        if(parent[x] != x){
19            parent[x] = find(parent[x]);
20        }
21        return parent[x];
22    };
23    
24    void unite(string x, string y){
25        parent[find(x)] = find(y);
26    };
27};
28
29class Solution {
30public:
31    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
32        set<string> vertices;
33        map<vector<string>, double> edges;
34        
35        for(int i = 0; i < equations.size(); i++){
36            vector<string> equation = equations[i];
37            double value = values[i];
38            
39            vertices.insert(equation[0]);
40            vertices.insert(equation[1]);
41            edges[equation] = value;
42            edges[{equation[1], equation[0]}] = 1/value;
43        }
44        
45        //check how many connected components there are?
46        DSU dsu(vertices);
47        for(auto it = edges.begin(); it != edges.end(); it++){
48            vector<string> edge = it->first;
49            dsu.unite(edge[0], edge[1]);
50        }
51        
52        //key: root of that group
53        map<string, set<string>> groups;
54        for(string vertex : vertices){
55            groups[dsu.find(vertex)].insert(vertex);
56        }
57        
58        // for(auto it = groups.begin(); it != groups.end(); ++it){
59        //     cout << it->first << " : ";
60        //     for(auto it2 = it->second.begin(); it2 != it->second.end(); ++it2){
61        //         cout << *it2 << " ";
62        //     }
63        //     cout << endl;
64        // }
65        
66        //for each connect component, full connect their nodes
67        //we have (a,b) and (b,c), now connect them to (a,c)
68        for(auto it = groups.begin(); it != groups.end(); it++){
69            vector<string> oneGroup(it->second.begin(), it->second.end());
70            
71            for(int i = 0; i < oneGroup.size(); i++){
72                for(int j = i; j < oneGroup.size(); j++){
73                    string vi = oneGroup[i], vj = oneGroup[j];
74                    if(i == j){
75                        edges[{vi, vj}] = 1.0;
76                    }else if(edges.find({vi, vj}) != edges.end()){
77                        //they are connected directly
78                        
79                    }else{
80                        // cout << vi << " and " << vj << " are connected indirectly" << endl;
81                        //they can be connected indirectly
82                        //find vertex vk s.t. {vi,vk} and {vk,vj} exist
83                        for(int k = 0; k < oneGroup.size(); k++){
84                            string vk = oneGroup[k];
85                            if(edges.find({vi, vk}) != edges.end() && edges.find({vj, vk}) != edges.end()){
86                                edges[{vi,vj}] = edges[{vi,vk}] * edges[{vk, vj}];
87                                edges[{vj,vi}] = edges[{vj,vk}] * edges[{vk, vi}];
88                                break;
89                            }
90                        }
91                    }
92                }
93            }
94        }
95        
96        // for(auto it = edges.begin(); it != edges.end(); ++it){
97        //     cout << it->first[0] << ", " << it->first[1] << endl;
98        // }
99        
100        vector<double> ans;
101        
102        for(vector<string>& query: queries){
103            string numerator = query[0];
104            string denominator = query[1];
105            if(edges.find({numerator, denominator}) != edges.end()){
106                ans.push_back(edges[{numerator, denominator}]);
107            }else{
108                ans.push_back(-1.0);
109            }
110        }
111    
112        return ans;
113    }
114};
115
116//Floyd Warshall
117//https://leetcode.com/problems/evaluate-division/discuss/88175/9-lines-%22Floydu2013Warshall%22-in-Python
118//Runtime: 8 ms, faster than 9.29% of C++ online submissions for Evaluate Division.
119//Memory Usage: 8.3 MB, less than 12.40% of C++ online submissions for Evaluate Division.
120class Solution {
121public:
122    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
123        unordered_map<string, unordered_map<string, double>> quot;
124        
125        int N = equations.size();
126        
127        for(int i = 0; i < N; i++){
128            vector<string> equation = equations[i];
129            string num = equation[0], den = equation[1];
130            double value = values[i];
131            quot[num][num] = 1.0;
132            quot[den][den] = 1.0;
133            quot[num][den] = value;
134            quot[den][num] = 1.0/value;
135        }
136        
137        for(auto it = quot.begin(); it != quot.end(); it++){
138            const string& k = it->first;
139            for(auto it2 = quot[k].begin(); it2 != quot[k].end(); it2++){
140                const string& i = it2->first;
141                for(auto it3 = quot[k].begin(); it3 != quot[k].end(); it3++){
142                    const string& j = it3->first;
143                    quot[i][j] = quot[i][k] * quot[k][j];
144                    // adding this line gives WA!!!
145                    //we don't need this line because i is not necessary <= j!
146                    // quot[j][i] = quot[j][k] * quot[k][i];
147                }
148            }
149        }
150        
151        vector<double> ans;
152        
153        for(vector<string>& query : queries){
154            string num = query[0], den = query[1];
155            
156            if(quot[num].find(den) == quot[num].end()){
157                ans.push_back(-1.0);
158            }else{
159                ans.push_back(quot[num][den]);
160            }
161        }
162        
163        return ans;
164    }
165};
166
167//DFS
168//https://leetcode.com/problems/evaluate-division/discuss/171649/1ms-DFS-with-Explanations
169//Runtime: 0 ms, faster than 100.00% of C++ online submissions for Evaluate Division.
170//Memory Usage: 8.5 MB, less than 7.50% of C++ online submissions for Evaluate Division.
171class Solution {
172public:
173    double dfs(string start, string end, unordered_map<string, unordered_map<string, double>>& graph, unordered_set<string>& visited){
174        if(graph.find(start) == graph.end()){
175            return -1;
176        }
177        
178        if(graph[start].find(end) != graph[start].end()){
179            return graph[start][end];
180        }
181        
182        for(const auto& nei : graph[start]){
183            if(find(visited.begin(), visited.end(), nei.first) != visited.end())
184                continue;
185            visited.insert(nei.first);
186            double res;
187            if((res = dfs(nei.first, end, graph, visited)) != -1){
188                //optimization: runtime 4ms -> 0ms
189                return graph[start][end] = graph[start][nei.first] * res;
190            }
191        }
192        
193        return -1;
194    }
195    
196    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
197        unordered_map<string, unordered_map<string, double>> quot;
198        
199        int N = equations.size();
200        
201        for(int i = 0; i < N; i++){
202            const vector<string>& equation = equations[i];
203            const string& num = equation[0], den = equation[1];
204            const double& value = values[i];
205            quot[num][num] = 1.0;
206            quot[den][den] = 1.0;
207            quot[num][den] = value;
208            quot[den][num] = 1.0/value;
209        }
210        
211        vector<double> ans;
212        unordered_set<string> visited;
213        
214        for(const vector<string>& query : queries){
215            const string& num = query[0], den = query[1];
216            visited = {num};
217            ans.push_back(dfs(num, den, quot, visited));
218        }
219        
220        return ans;
221    }
222};
223
224//BFS
225//https://leetcode.com/problems/evaluate-division/discuss/88275/Python-fast-BFS-solution-with-detailed-explantion
226//Runtime: 4 ms, faster than 58.89% of C++ online submissions for Evaluate Division.
227//Memory Usage: 8.5 MB, less than 8.40% of C++ online submissions for Evaluate Division.
228class Solution {
229public:
230    double dfs(string start, string end, unordered_map<string, unordered_map<string, double>>& graph, unordered_set<string>& visited){
231        if(graph.find(start) == graph.end()){
232            return -1;
233        }
234        
235        if(graph[start].find(end) != graph[start].end()){
236            return graph[start][end];
237        }
238        
239        for(const auto& nei : graph[start]){
240            if(find(visited.begin(), visited.end(), nei.first) != visited.end())
241                continue;
242            visited.insert(nei.first);
243            double res;
244            if((res = dfs(nei.first, end, graph, visited)) != -1){
245                //optimization: runtime 4ms -> 0ms
246                return graph[start][end] = graph[start][nei.first] * res;
247            }
248        }
249        
250        return -1;
251    }
252    
253    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
254        unordered_map<string, unordered_map<string, double>> quot;
255        
256        int N = equations.size();
257        
258        for(int i = 0; i < N; i++){
259            const vector<string>& equation = equations[i];
260            const string& num = equation[0], den = equation[1];
261            const double& value = values[i];
262            quot[num][num] = 1.0;
263            quot[den][den] = 1.0;
264            quot[num][den] = value;
265            quot[den][num] = 1.0/value;
266        }
267        
268        vector<double> ans;
269        unordered_set<string> visited;
270        
271        for(const vector<string>& query : queries){
272            const string& num = query[0], den = query[1];
273            
274            double prod;
275            
276            //cout << num << " - " << den << endl;
277            
278            if(quot.find(num) == quot.end() || quot.find(den) == quot.end()){
279                //cout << "cannot find " << num << " or " << den << endl;
280                prod = -1.0;
281            }else if(quot[num].find(den) != quot[num].end()){
282                //cout << num << " - " << den << " already exist" << endl;
283                prod = quot[num][den];
284            }else{
285                //cout << "do BFS" << endl;
286                prod = -1.0;
287                
288                //do BFS
289                //(node, current product)
290                queue<pair<string, double>> q;
291                q.push({num, 1.0});
292                visited = {num};
293            
294                while(!q.empty()){
295                    //cannot use pair<string, double>& here!!
296                    pair<string, double> cur = q.front(); q.pop();
297
298                    if(cur.first == den){
299                        prod = cur.second;
300                        break;
301                    }
302                    
303                    for(const pair<string, double>& nei : quot[cur.first]){
304                        if(visited.find(nei.first) != visited.end()) 
305                            continue;
306                        visited.insert(nei.first);
307                        /*
308                        quot represents for the weight of edges,
309                        not accumulative product,
310                        so following line is wrong!
311                        quot[cur.first][nei.first] = cur.second * nei.second;
312                        */
313                        q.push({nei.first, cur.second * nei.second});
314                    }
315                }
316            }
317            
318            //cout << prod << endl;
319            ans.push_back(prod);
320        }
321        
322        return ans;
323    }
324};

Cost

Complexity

Time
O(n) to O(n log n), depending on the dominant loop or data structure operation
Dominated by the main traversal, recursion, or data-structure operations in the code.
Space
O(n) in the usual case for auxiliary containers or recursion
Auxiliary state plus the answer structure where the problem requires one.