399. Evaluate Division
Equations are given in the format A / B = k, where A and B are variables represented as strings, and k is a real number (floating point number). Given some queries, return the answers. If the answer does not exist, return -1.0.
Example:
Given a / b = 2.0, b / c = 3.0
.
queries are: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
.
return[6.0, 0.5, -1.0, 1.0, -1.0 ]
.
The input is: vector<pair<string, string>> equations
, vector<double>& values
, vector<pair<string, string>> queries
, where equations.size() == values.size()
, and the values are positive. This represents the equations. Return vector<double>
.
According to the example above:
equations = [ ["a", "b"], ["b", "c"] ],
values = [2.0, 3.0],
queries = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ].
花花:https://www.youtube.com/watch?v=UwpvInpgFmo
方法1: dfs
建立所有已知关系的双向图,以乘积为weight,然后用寻找queries当中路径的weight乘积。如果存在路径返回weight,如果不存在返回-1。起点和终点中有任意值不在图中返回-1。在每一个query中要沿途传递一个visitied,来记录已经访问过的路径,避免cycle。
需要的数据结构:
- unordered_map<string, unordered_map<string, double>>>>: {start : {end: weight}} 建立图, 这里选择unordered_map 主要由于没有什么有序性,对比207,210,course schedule,用到vector<vector<int>>, 310. minimum height tree,用到vector<unordered_set>
- unordered_set<string>: 对每一次query,记录经过的节点
- vector<double>:储存结果
易错点:
- 终止条件:当a == b,返回1.0
- 建立双向图,weight和1/weight
- 一定要预先判断start和end的存在:不只是为了efficiency,而是[“x”, “x”] 会返回1.0 而不是-1.0
- 如果不存在对应路径,最后会返回-1,如果存在返回graph[a][c] * dfs(c, b, graph, visited)
- 递归时要记录visited.insert(a): 作为双向图,也就是一定有环的图,必须记录visited
Complexity
时间: O(e + q * e), 如果每个query都需要所有节点,O(q * e), O(e) 是建立图的时间
空间:O(e)
grandyang: http://www.cnblogs.com/grandyang/p/5880133.html
class Solution {
public:
vector<double> calcEquation(vector<vector<string>> equations, vector<double>& values, vector<vector<string>> queries) {
vector<double> result;
unordered_map<string, unordered_map<string, double>> hash;
// construct graph
for (int i = 0; i < equations.size(); i++) {
hash[equations[i][0]][equations[i][1]] = values[i];
hash[equations[i][1]][equations[i][0]] = 1 / values[i];
}
// query
for (auto q: queries) {
if (!hash.count(q[0]) || !hash.count(q[1])) result.push_back(-1.0);
else {
unordered_set<string> visited;
result.push_back(calcHelper(hash, q[0], q[1],visited));
}
}
return result;
}
double calcHelper(unordered_map<string, unordered_map<string, double>> & hash, string start, string end, unordered_set<string> & visited) {
if (start == end) return 1.0;
for (auto item: hash[start]){
string intermediate = item.first;
double segment = item.second;
if (visited.count(intermediate)) continue;
visited.insert(intermediate);
double tmp = calcHelper(hash, intermediate, end, visited);
if (tmp != -1) return tmp * segment;
}
return -1.0;
}
};
花花:https://www.youtube.com/watch?v=UwpvInpgFmo
class Solution {
public:
vector<double> calcEquation(vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries) {
vector<double> result;
// 给所有equations建立双向图
unordered_map<string, unordered_map<string, double>> graph;
for (int i = 0; i < equations.size(); i++){
string A = equations[i].first;
string B = equations[i].second;
double weight= values[i];
graph[A][B] = weight;
graph[B][A] = 1 / weight;
}
for (auto q: queries){
string a = q.first;
string b = q.second;
if (!graph.count(a) || !graph.count(b)){
result.push_back(-1);
continue;
}
// 为每次dfs建立一个visited集合,来避免cycle
unordered_set<string> visited;
double res = calcHelper(a, b, graph, visited);
result.push_back(res);
}
return result;
}
private:
double calcHelper(string& a, string& b, unordered_map<string, unordered_map<string, double>> & graph, unordered_set<string> & visited){
// 终止条件是
if (a == b) return 1.0;
visited.insert(a);
for (auto pair: graph[a]){
string c = pair.first;
if (!visited.count(c)){
double d = calcHelper(c, b, graph, visited);
if (d != -1) {
return d * graph[a][c];
}
}
}
return -1.0;
}
};
方法2:Union Find
Complexity
时间:O(e + q)
空间: O(e)
Union find可以在O(1)时间查找parent和path
易错点:
- child / parent = weight
- 更新祖先,同时要更新祖先的weight
// Author: Huahua
// Runtime: 3 ms
class Solution {
public:
vector<double> calcEquation(const vector<pair<string, string>>& equations, vector<double>& values, const vector<pair<string, string>>& queries) {
// parents["A"] = {"B", 2.0} -> A = 2.0 * B
// parents["B"] = {"C", 3.0} -> B = 3.0 * C
unordered_map<string, pair<string, double>> parents;
for (int i = 0; i < equations.size(); ++i) {
const string& A = equations[i].first;
const string& B = equations[i].second;
const double k = values[i];
// Neighter is in the forrest
if (!parents.count(A) && !parents.count(B)) {
parents[A] = {B, k};
parents[B] = {B, 1.0};
} else if (!parents.count(A)) {
parents[A] = {B, k};
} else if (!parents.count(B)) {
parents[B] = {A, 1.0 / k};
} else {
auto& rA = find(A, parents);
auto& rB = find(B, parents);
parents[rA.first] = {rB.first, k / rA.second * rB.second};
}
}
vector<double> ans;
for (const auto& pair : queries) {
const string& X = pair.first;
const string& Y = pair.second;
if (!parents.count(X) || !parents.count(Y)) {
ans.push_back(-1.0);
continue;
}
auto& rX = find(X, parents); // {rX, X / rX}
auto& rY = find(Y, parents); // {rY, Y / rY}
if (rX.first != rY.first)
ans.push_back(-1.0);
else // X / Y = (X / rX / (Y / rY))
ans.push_back(rX.second / rY.second);
}
return ans;
}
private:
pair<string, double>& find(const string& C, unordered_map<string, pair<string, double>>& parents) {
if (C != parents[C].first) {
const auto& p = find(parents[C].first, parents);
parents[C].first = p.first;
parents[C].second *= p.second;
}
return parents[C];
}
};