题目:跳转至 399. 除法求值
给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] = [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi = values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。
另有一些以数组 queries 表示的问题,其中 queries[j] = [Cj, Dj] 表示第 j 个问题,请你根据已知条件找出 Cj / Dj = ? 的结果作为答案。
返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案。
注意: 输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。
示例 1:
输入:equations = [[“a”,“b”],[“b”,“c”]], values = [2.0,3.0], queries = [[“a”,“c”],[“b”,“a”],[“a”,“e”],[“a”,“a”],[“x”,“x”]]
输出:[6.00000,0.50000,-1.00000,1.00000,-1.00000]
解释:
条件: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 ]
示例 2:
输入:equations = [[“a”,“b”],[“b”,“c”],[“bc”,“cd”]], values = [1.5,2.5,5.0], queries = [[“a”,“c”],[“c”,“b”],[“bc”,“cd”],[“cd”,“bc”]]
输出:[3.75000,0.40000,5.00000,0.20000]
示例 3:
输入:equations = [[“a”,“b”]], values = [0.5], queries = [[“a”,“b”],[“b”,“a”],[“a”,“c”],[“x”,“y”]]
输出:[0.50000,2.00000,-1.00000,-1.00000]
提示:
class Solution {
public:
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
}
};
思路:
思路可以有,把 equations 变量对中存在相同变量的值统一管理,如 [[“a”,“b”],[“b”,“c”]] 中的 “a”,“b”,“c” 都可以用 “a” 来表示,但就是写不出来,学习题解。
把问题建模成图,各个变量就是点,变量之间的比值就是边的权值,问题就成了求任意两点间的路径长。
方法一:广度优先搜索
class Solution {
public:
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
int nvars=0;
unordered_map<string,int> variables;
int n=equations.size();
for(int i=0;i<n;++i){ //遍历equations中出现过的值,通过哈希表把nvars的值赋给他们(即把字符串映射成整数)
if(variables.find(equations[i][0])==variables.end())
variables[equations[i][0]]=nvars++;
if(variables.find(equations[i][1])==variables.end())
variables[equations[i][1]]=nvars++;
}
vector<vector<pair<int,double>>> edges(nvars); //记录每个点直接连接到的所有点及对应权值
for(int i=0;i<n;++i){
int va=variables[equations[i][0]];
int vb=variables[equations[i][1]];
edges[va].push_back(make_pair(vb,values[i])); //edges[va]记录对应vb及va/vb的值,即记录边va->vb及其权值
edges[vb].push_back(make_pair(va,1.0/values[i])); //edges[vb]记录va及vb/va的值,即记录边vb->va及其权值
}
vector<double> ret;
for(const auto& q:queries){
double result=-1.0; //初始化每个值为无解状态 -1.0
if(variables.find(q[0])!=variables.end() && variables.find(q[1])!=variables.end()){ //两个值都找到肯定能解
int ia=variables[q[0]]; //把字符串变为其对应的整数
int ib=variables[q[1]];
if(ia==ib)
result=1.0;
else{
queue<int> points;
points.push(ia); //从起点出发,先把当前除数放入队列中
vector<double> ratios(nvars,-1.0); //初始化ia/ib比例个数为所有出现字符串的总数,且都无解
ratios[ia]=1.0; //对应放入自身比例,ia/ia=1.0
while(!points.empty() && ratios[ib]<0){ //如果队列中有值且ia/ib的比例一直没找到
int x=points.front(); //获取队首的点
points.pop();
for(const auto [y,val]:edges[x]){ //遍历点对应的边集
if(ratios[y]<0){ //如果x/y比例未更新
ratios[y]=ratios[x]*val; //相乘更新比例(遍历edges[ib]时,ratios(ib)=(ia/ia)*(ia/ib),则ratios(y)=(ia/ia)*(ia/ib)*(ib/y))
points.push(y); //放入点y
}
}
}
result=ratios[ib];
}
}
ret.push_back(result);
}
return ret;
}
};
方法二:Floyd 算法
如果查询数量很多,每次查询都独立搜索一次的效率会很低,可以预先计算出任意两点之间的距离,这样在查询时只要读取计算后的值就可以了。
class Solution {
public:
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
int nvars=0;
unordered_map<string,int> variables;
int n=equations.size();
for(int i=0;i<n;++i){ //遍历equations中出现过的值,把nvars的值赋给他们(即把字符串映射成整数)
if(variables.find(equations[i][0])==variables.end())
variables[equations[i][0]]=nvars++;
if(variables.find(equations[i][1])==variables.end())
variables[equations[i][1]]=nvars++;
}
vector<vector<double>> graph(nvars,vector<double>(nvars,-1.0)); //初始化出一个nvaes*nvars大小的矩阵存储任两点间的距离
for(int i=0;i<n;++i){
int va=variables[equations[i][0]];
int vb=variables[equations[i][1]];
graph[va][vb]=values[i]; //记录边va->vb及其权值
graph[vb][va]=1.0/values[i]; //记录边vb->va及其权值
}
for(int k=0;k<nvars;++k){
for(int i=0;i<nvars;++i){
for(int j=0;j<nvars;++j){
if(graph[i][k]>0 && graph[k][j]>0) //如果存在计算关系
graph[i][j]=graph[i][k]*graph[k][j]; //计算任两点间的比例,a/b * b/c=a/c
}
}
}
vector<double> ret;
for(const auto& q:queries){
double result=-1.0; //初始化每个值为无解状态 -1.0
if(variables.find(q[0])!=variables.end() && variables.find(q[1])!=variables.end()){ //两个值都找到肯定能解
int ia=variables[q[0]]; //把字符串变为其对应的整数
int ib=variables[q[1]];
if(graph[ia][ib]>0)
result=graph[ia][ib];
}
ret.push_back(result);
}
return ret;
}
};
方法三:带权并查集
class Solution {
public:
int findf(vector<int>& f, vector<double>& w, int x) { //寻找根节点
if (f[x] != x) { //如果自己的根节点不是自己
int father = findf(f, w, f[x]); //递归寻找,x的根节点是f[x],...,直至f[x]==f[x],找到最终的根节点
w[x] = w[x] * w[f[x]]; //x的权值更新为与根节点权值的乘积,如a/b=2,b/c=3,那么a/c=a/b * b/c
f[x] = father; //记录x的根节点
}
return f[x];
}
void merge(vector<int>& f, vector<double>& w, int x, int y, double val) { //合并两个集合
int fx = findf(f, w, x); //寻找各自根节点
int fy = findf(f, w, y);
if(fx!=fy){ //如果根节点不同
f[fx] = fy; //合并把x的根节点放在y的根节点下,即y的根节点是x的根节点
w[fx] = val * w[y] / w[x]; //更新x根节点的权值
//如果val为a/d=6(a的根节点为d),已知现在a/b=3(w[a]=3,根节点b),d/c=4(w[d]=4,根节点c),
//合并a,d所在集合,把a的根节点b放在d的根节点c下,w[b](根节点c,即求b/c)=a/d * d/c * b/a=a/d * d/c / (a/b)=val*w[d]/w[a]
}
}
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
int nvars=0;
unordered_map<string,int> variables;
int n=equations.size();
for(int i=0;i<n;++i){ //遍历equations中出现过的值,通过哈希表把nvars的值赋给他们(即把字符串映射成整数)
if(variables.find(equations[i][0])==variables.end())
variables[equations[i][0]]=nvars++;
if(variables.find(equations[i][1])==variables.end())
variables[equations[i][1]]=nvars++;
}
vector<int> f(nvars);
vector<double> w(nvars,1.0); //自己为自己的根节点时,权值为1
for(int i=0;i<nvars;++i)
f[i]=i; //初始化自己为自己的根节点
for(int i=0;i<n;++i){
int va=variables[equations[i][0]];
int vb=variables[equations[i][1]];
merge(f,w,va,vb,values[i]); //合并两个节点
}
vector<double> ret;
for(const auto& q:queries){
double result=-1.0; //初始化每个值为无解状态 -1.0
if(variables.find(q[0])!=variables.end() && variables.find(q[1])!=variables.end()){ //两个值都找到肯定能解
int ia=variables[q[0]]; //把字符串变为其对应的整数
int ib=variables[q[1]];
int fa=findf(f,w,ia); //寻找对应的根节点
int fb=findf(f,w,ib);
if(fa==fb) //如果根节点相同直接求出比值
result=w[ia]/w[ib];
}
ret.push_back(result);
}
return ret;
}
};