The following problem on leetcode has 2 described solution. Let N be the number of input equations and M be the number of queries:
- One uses union find and is O((M+N)log*(N))
- One uses DFS and is O(M*N)
However it seems to me that answering all queries at the end with DFS will have an O(M+N) runtime. The below code passed all tests and was accepted by the OJ.
General outline
- Build a graph. Each equation (a/b) = x creates two weighted edges from a to b with weight x, and from b to a with weight 1/x.
- I run DFS over all variables and record the connected components. For each letter, I maintain in which connected components it is via component_map.
- Each var in the component has a value V = captain/var, where captain was the first variable inserted
- Then for each query I can answer when both belongs to the same component without need of backtraking since (captain/var1* var2/captain = var2/var1)
The key differences between my DFS solution and theirs are:
- I do not need to backtrack due to last bullet above
- I answer all queries at once at the end
My reasoning is that every single operation I do is amortized O(1), basically with hash maps and vectors. I run DFS inside a loop with N iteration, but the sum complexity of all my DFS calls will be O(M+N) as every node is only visited once.
I hence believe this solution to be O(M+N).
Question: Am I correct? Can you prove the time complexity of this algorithm whatever it is?
class Solution {
public:
typedef unordered_map<string,double> component; // var --> captain/var
typedef unordered_map<string,component> components; // captain --> component , each component identified by its captain.
typedef unordered_map<string,string> component_map; // var --> captain, to what component this var belongs?
typedef unordered_map<string, vector<pair<string,double>>> adjacency_list;
void DFS(const string& node, component& compo, adjacency_list& adj, double value, unordered_set<string>& visited,component_map& m, const string& captain)
{
for(auto p: adj[node])
{
if(compo.find(p.first) == compo.end())
{
visited.insert(p.first);
m.insert({p.first,captain}); // this letter belongs to this "captain component"
compo.insert({p.first,value*p.second}); // insert L,V
DFS(p.first,compo,adj,value*p.second,visited,m,captain);
}
}
}
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries)
{
adjacency_list adj;
for(int i=0;i<equations.size();++i)
{
string a = equations[i][0];
string b = equations[i][1];
double v = values[i];
auto it = adj.find(a);
if( it == adj.end())
{
adj.insert({a,{}});
it = adj.find(a);
}
it->second.push_back({b,v});
it = adj.find(b);
if( it == adj.end())
{
adj.insert({b,{}});
it = adj.find(b);
}
it->second.push_back({a,1/v});
}
components cps;
unordered_set<string> visited;
component_map m;
for(int i=0;i<equations.size();++i)
{
string a = equations[i][0];
if(visited.find(a)==visited.end())
{
auto it = cps.insert({a,{}}).first;
DFS(a,it->second,adj,1,visited,m,a);
}
string b = equations[i][1];
if(visited.find(b)==visited.end())
{
auto it = cps.insert({b,{}}).first;
DFS(b,it->second,adj,1,visited,m,a);
}
}
vector<double> res;
for(auto& q:queries)
{
auto it0 = m.find(q[0]);
auto it1 = m.find(q[1]);
if(it0 != m.end() && it1 != m.end() && it0->second == it1->second)
{
auto& captain = it0->second;
auto& cp = cps[captain];
res.push_back(cp[q[1]]/cp[q[0]]);
}
else
{
res.push_back(-1.0);
}
}
return res;
}
};