描述
给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] = [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi = values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。
另有一些以数组 queries 表示的问题,其中 queries[j] = [Cj, Dj] 表示第 j 个问题,请你根据已知条件找出 Cj / Dj = ? 的结果作为答案。
返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -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]
提示:
1 <= equations.length <= 20
equations[i].length == 2
1 <= Ai.length, Bi.length <= 5
values.length == equations.length
0.0 < values[i] <= 20.0
1 <= queries.length <= 20
queries[i].length == 2
1 <= Cj.length, Dj.length <= 5
Ai, Bi, Cj, Dj 由小写英文字母与数字组成
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/evaluate-division/
特别说明
求解中采用了三种方法方法一、二采用广度优先搜索,只是两种实现中图的存储方式不一样,方法一种采用邻接矩阵存储,方法二中采用邻接表存储; 方法三采用flyod算法,不管哪种方法都是图中的求两个点间的最短路径办法,也就是图中可能存在多条路径,那值就可能不一致,为什么可以用来解决这个问题???请特别注意题的描述:“输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果”,不存在任何矛盾的结果!!!那肯定在两点间要么不存在路径,要么存在也仅仅只有一条,当然就可以采用如上的方法求解。
求解
class Solution {
private:
const double INVALID = -1.0;
// 方法一辅助函数
// 对图graph从顶点 v开始进行广度优先遍历,直到遇到w结束或者直到遍历完成
// 注意,这儿是从某个顶点开始广度优先遍历,并非对整个图进行广度遍历
void bfsTraverse(vector<vector<double>> &graph, int a, int b) {
const int N = graph.size();
// 广度优先遍历并更新权值
vector<bool> visited(N, false);
queue<int> q;
q.push(a);
visited[a] = true;
// 一旦遍历到b点,立即结束遍历
while (!q.empty() && !visited[b]) {
int v = q.front();
q.pop();
// 遍历v邻接点
for (int j = 0; j < N; ++j) {
// 1)j未被遍历 2)j是v的邻接点 3)a到v可达,这样才可能a->v->j
if (graph[v][j] != INVALID && !visited[j] && graph[a][v] != INVALID) {
graph[a][j] = graph[a][v] * graph[v][j];
q.push(j);
visited[j] = true;
}
}
}
}
public:
// 方法一,构造有向带权图实现,采用邻接矩阵存储
// 缺点:1)存储采用邻接矩阵有点浪费
// 2)每次为了遍历一个新的a -> b,拷贝了一份图的存储,目的是遍历过程中修改值,存储和效率上的瑕疵
vector<double>
calcEquation_1e(vector<vector<string>> &equations, vector<double> &values, vector<vector<string>> &queries) {
if (equations.empty()) {
return vector<double>();
}
int vIndex = 0; // 顶点初始编号
unordered_map<string, int> vHashMap; // 顶点到数值的映射
for (auto &vec : equations) {
if (vHashMap.count(vec[0]) == 0) {
vHashMap.emplace(vec[0], vIndex++);
}
if (vHashMap.count(vec[1]) == 0) {
vHashMap.emplace(vec[1], vIndex++);
}
}
// 用二维矩阵即邻接矩阵存储图的信息
vector<vector<double>> graph(vIndex, vector<double>(vIndex, INVALID));
for (int i = 0; i < vIndex; ++i) {
graph[i][i] = 1.0;
}
for (int i = 0; i < equations.size(); ++i) {
int v = vHashMap[equations[i][0]]; // 顶点v的数值映射
int w = vHashMap[equations[i][1]]; // 顶点w的数值映射
graph[v][w] = values[i];
graph[w][v] = 1.0 / values[i];
}
vector<double> res;
for (int i = 0; i < queries.size(); ++i) {
if ((vHashMap.count(queries[i][0])) > 0 && (vHashMap.count(queries[i][1]) > 0)) {
// 待求得点均在已构建的图中
auto gcp = graph;
int a = vHashMap[queries[i][0]];
int b = vHashMap[queries[i][1]];
bfsTraverse(gcp, a, b);
res.push_back(gcp[a][b]);
continue;
}
// 待求点未知
res.push_back(-1);
}
return res;
}
// 方法二,构造有向带权图实现,采用邻接表实现,且遍历中不再采用辅助函数,也不用额外拷贝图的存储
vector<double>
calcEquation_2e(vector<vector<string>> &equations, vector<double> &values, vector<vector<string>> &queries) {
if (equations.empty()) {
return vector<double>();
}
int vIndex = 0; // 顶点初始编号
unordered_map<string, int> vHashMap; // 顶点到数值的映射
for (auto &vec : equations) {
if (vHashMap.count(vec[0]) == 0) {
vHashMap.emplace(vec[0], vIndex++);
}
if (vHashMap.count(vec[1]) == 0) {
vHashMap.emplace(vec[1], vIndex++);
}
}
// 采用邻接表构造图
vector<vector<std::pair<int, double>>> edges(vIndex);
for (int i = 0; i < equations.size(); ++i) {
int v = vHashMap[equations[i][0]]; // 顶点v的数值映射
int w = vHashMap[equations[i][1]]; // 顶点w的数值映射
edges[v].emplace_back(w, values[i]);
edges[w].emplace_back(v, 1.0 / values[i]);
}
// 根据待求每对点进行广度优先遍历
vector<double> res;
for (int i = 0; i < queries.size(); ++i) {
// 待求点均在已构建的图中
if ((vHashMap.count(queries[i][0])) > 0 && (vHashMap.count(queries[i][1]) > 0)) {
int a = vHashMap[queries[i][0]];
int b = vHashMap[queries[i][1]];
// 待求点相等,无需遍历
if (a == b) {
res.push_back(1.0);
continue;
}
// 待求点不等,需要遍历
vector<double> ratios(vIndex, -1.0);
queue<int> q;
q.push(a);
ratios[a] = 1.0;
while (!q.empty() && (ratios[b] == -1.0)) {
int v = q.front();
q.pop();
// 关注下一行的C++新特性
for (const auto &[w, weight] : edges[v]) {
// 遍历v的所有邻接点
if (ratios[w] == -1.0) {
ratios[w] = ratios[v] * weight;
q.push(w);
}
}
}
res.push_back(ratios[b]);
continue;
}
// 待求点未知,即不在构建的图中
res.push_back(-1);
}
return res;
}
// 方法三,构造有向带权图实现,采用邻接矩阵存储,使用floyd算法求出任意两点间的值(假如存在),而不用每次进行遍历查询
vector<double>
calcEquation(vector<vector<string>> &equations, vector<double> &values, vector<vector<string>> &queries) {
if (equations.empty()) {
return vector<double>();
}
int vIndex = 0; // 顶点初始编号
unordered_map<string, int> vHashMap; // 顶点到数值的映射
for (auto &vec : equations) {
if (vHashMap.count(vec[0]) == 0) {
vHashMap.emplace(vec[0], vIndex++);
}
if (vHashMap.count(vec[1]) == 0) {
vHashMap.emplace(vec[1], vIndex++);
}
}
// 用二维矩阵即邻接矩阵存储图的信息
vector<vector<double>> graph(vIndex, vector<double>(vIndex, INVALID));
for (int i = 0; i < vIndex; ++i) {
graph[i][i] = 1.0;
}
for (int i = 0; i < equations.size(); ++i) {
int v = vHashMap[equations[i][0]]; // 顶点v的数值映射
int w = vHashMap[equations[i][1]]; // 顶点w的数值映射
graph[v][w] = values[i];
graph[w][v] = 1.0 / values[i];
}
// floyd算法遍历,求出任意两点间的值
for (int i = 0; i < vIndex; ++i) {
for (int v = 0; v < vIndex; ++v) {
for (int w = 0; w < vIndex; ++w) {
if (graph[v][i] != INVALID && graph[i][w] != INVALID) {
graph[v][w] = graph[v][i] * graph[i][w];
}
}
}
}
// 通过表查询待求点对的值
vector<double> res;
for (int i = 0; i < queries.size(); ++i) {
// 待求点均在已构建的图中
if ((vHashMap.count(queries[i][0])) > 0 && (vHashMap.count(queries[i][1]) > 0)) {
int a = vHashMap[queries[i][0]];
int b = vHashMap[queries[i][1]];
res.push_back(graph[a][b]);
continue;
}
// 待求点未知,即不在构建的图中
res.push_back(-1);
}
return res;
}
};