399. 除法求值
方法:
将整个问题建模成一张图:给定图中的一些点(变量),以及某些边的权值(两个变量的比值),试对任意两点(两个变量)求出其路径长(两个变量的比值)。
class Solution {
public:
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
// 1.题目中给出:“如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案”,所以需要先从给出的变量对数组中整理出所有存在的不同的变量(字符串),相当于整理图的节点
// variables存储条件(变量对数组)中给出的变量
unordered_set<string> variables;
// 遍历方程组,建立变量与索引的映射关系
for(int i = 0; i < equations.size(); i++){
// 题目中给出“每个 Ai 或 Bi 是一个表示单个变量的字符串。”,说明变量对数组中的每个变量对中,可能只有一个元素,所以下边应该分别判断equations[i][0] 和 equations[i][1]
// 如果set中没有这个字符串,就加进去,使用set中不能有有重复元素的特性,能够方便地整理条件中的变量
if(!variables.count(equations[i][0])){
variables.emplace(equations[i][0]);
}
if(!variables.count(equations[i][1])){
variables.emplace(equations[i][1]);
}
}
// 2. 完善图中的节点邻居信息和路径的权值信息
// 对于每个点(也就是字符串代表的变量),存储与其直接相连的所有节点以及对应的权值;第一个string就是当前的节点,vector<pair<string, double>>>中的string就是邻居节点,double就是对应二者的商值
unordered_map<string, vector<pair<string, double>>> edges;
for(int i = 0; i < equations.size(); i++){
string Ai = equations[i][0];
string Bi = equations[i][1];
// 添加双向边,权值为给定的商值和其倒数
edges[Ai].push_back(make_pair(Bi, values[i]));
edges[Bi].push_back(make_pair(Ai, 1.0 / values[i]));
}
// 3. 使用BFS计算queries:过程相当于暂时更新所有从Cj出发的节点的值,更新方式就是用前一段路径的权值✖后一段路径的权值,因为是从Cj开始,所以广度优先遍历完,就相当于记录了所有Cj除以其他节点的结果,放到ratios数组中。
// 存储queries中每一对的计算结果
vector<double> queryResults;
// 遍历问题数组,计算结果
for (const auto& query: queries) {
// 如果无法确定答案或者出现了条件中没有的字符串,都返回-1,所以将result初始化为-1
double result = -1.0;
// 如果要计算的query的两个变量都是已有的变量(因为set中不能有重复的元素,所以count的结果=1就表示有这个元素,=0就表示没有这个元素,不可能再出现其他的数值,这样就能判断set中是否有这个元素)
if (variables.count(query[0]) && variables.count(query[1])) {
// Cj和Dj
string Cj = query[0], Dj = query[1];
// 如果是同一个变量,商值为1
if (Cj == Dj) {
result = 1.0;
}
else { // 就说明两个变量不相同,而且二者在图中不一定相邻,所以就需要使用广度优先搜索计算变量之间的商值
// ratios数组用于暂时保存从Cj遍历到其他所有节点的过程中计算的值。
unordered_map<string, double> ratios;
// Cj是开始的地方,所以赋值为1即可
ratios[Cj] = 1.0;
// 广度优先遍历,使用队列辅助;
queue<string> points;
// 先把相当于根节点的Cj入队
points.push(Cj);
// 遍历图中的节点,更新变量之间的关系;
// 如果map中有了Dj,就说明遍历过程中,已经计算到了Dj,就能退出循环了
// 还有可能没有能够到达Dj的路径,就是正常的BFS结束
while (!points.empty() && !ratios.count(Dj)) {
// 广度优先搜索的流程
string x = points.front();
points.pop();
// 遍历与当前变量直接相连的变量
for (const auto [y, val]: edges[x]) {
// 表示还没计算到当前节点
if (!ratios.count(y)) {
// 更新Cj除以当前节点变量的商值,并将下一个变量加入队列
ratios[y] = ratios[x] * val;
points.push(y);
}
}
}
// 如果是找到了Dj,那么ratios[Dj]中就是保存的从Cj一路计算,直到找到Dj时计算的Cj/Dj的结果
if(ratios.count(Dj)){
result = ratios[Dj];
}
}
}
// 将计算结果添加到返回向量
queryResults.push_back(result);
}
// 返回queries中每一对的计算结果
return queryResults;
}
};
class Solution {
public:
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
// 1.题目中给出:“如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案”,所以需要先从给出的变量对数组中整理出所有存在的不同的变量(字符串),相当于整理图的节点
// 变量对的对数,用于循环遍历变量对数组
int equationsSize = equations.size();
// variables存储变量与索引的映射关系,通过哈希表将每个不同的字符串映射成整数。
unordered_map<string, int> variables;
// 变量的索引,用于后续方便查找
int nvars = 0;
// 遍历方程组,建立变量与索引的映射关系
for(int i = 0; i < equationsSize; i++){
// 题目中给出“每个 Ai 或 Bi 是一个表示单个变量的字符串。”,说明变量对数组中的每个变量对中,可能只有一个元素,所以下边应该分别判断equations[i][0] 和 equations[i][1],将其记录为存在的变量,并映射为整数;
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++;
}
}
// 2. 完善图中的节点邻居信息和路径的权值信息
// 对于每个点,存储与其直接相连的所有节点以及对应的权值;这里就能直接用索引值表示节点了
vector<vector<pair<int, double>>> edges(nvars);
for(int i = 0; i < equationsSize; i++){
int Ai = variables[equations[i][0]];
int Bi = variables[equations[i][1]];
// 添加双向边,权值为给定的商值和其倒数
edges[Ai].push_back(make_pair(Bi, values[i]));
edges[Bi].push_back(make_pair(Ai, 1.0 / values[i]));
}
// 3. 使用BFS计算queries:过程相当于暂时更新所有从Cj出发的节点的值,更新方式就是用前一段路径的权值✖后一段路径的权值,因为是从Cj开始,所以广度优先遍历完,就相当于记录了所有Cj除以其他节点的结果,放到ratios数组中。
// 存储queries中每一对的计算结果
vector<double> queryResults;
// 遍历问题数组,计算结果
for (const auto& query: queries) {
// 如果无法确定答案或者出现了条件中没有的字符串,都返回-1,所以将result初始化为-1
double result = -1.0;
// 如果要计算的query的两个变量都在映射表中
if (variables.find(query[0]) != variables.end() && variables.find(query[1]) != variables.end()) {
// query_Cj和query_Dj相当于query中的变量的索引
int query_Cj = variables[query[0]], query_Dj = variables[query[1]];
// 如果是同一个变量,商值为1
if (query_Cj == query_Dj) {
result = 1.0;
}
else { // 就说明两个变量不相同,而且二者在图中不一定相邻,所以就需要使用广度优先搜索计算变量之间的商值
// ratios数组用于暂时保存从Cj遍历到其他所有节点的过程中计算的值。
vector<double> ratios(nvars, -1.0);
// query_Cj是开始的地方,所以赋值为1即可
ratios[query_Cj] = 1.0;
// 广度优先遍历,使用队列辅助;
queue<int> points;
// 先把相当于根节点的query_Cj入队
points.push(query_Cj);
// 遍历图中的节点,更新变量之间的关系;如果ratios[query_Dj] >= 0了,就说明遍历过程中,已经计算到了Dj,就可以退出了
while (!points.empty() && ratios[query_Dj] < 0) {
// 广度优先搜索的流程
int x = points.front();
points.pop();
// 遍历与当前变量直接相连的变量
for (const auto [y, val]: edges[x]) {
// 表示还没计算到当前节点
if (ratios[y] < 0) {
// 更新Cj除以当前节点变量的商值,并将下一个变量加入队列
ratios[y] = ratios[x] * val;
points.push(y);
}
}
}
// ratios[query_Dj]中就保存了从Cj一路计算,直到找到Dj时计算的Cj/Dj的结果
result = ratios[query_Dj];
}
}
// 将计算结果添加到返回向量
queryResults.push_back(result);
}
// 返回queries中每一对的计算结果
return queryResults;
}
};