本质是图的拓扑排序应用。
给定一个包含 N N N个节点的有向图 G G G,我们给出它的节点编号的一种排列,如果满足:对于图 G G G的任意一条有向边 ( u , v ) (u,v) (u,v), u u u在排列中出现在 v v v的前面,则称该排列是图的拓扑排序。
拓扑排序有两种实现方式:
- 深度优先遍历 + 栈。
- 广度优先遍历 + 队列。
emmmm,也没什么好说的,与207.课程表的代码几乎一模一样。
方法:快慢指针。
把数组认为是一个有向图,然后利用快慢指针判断环的入口。
思路
最初看到题目时,并没有想到这是一道图论题目。
分析题目所给的示例,察觉到了这样一个信息:若题目给了 a b \frac{a}{b} ba,给了 b c \frac{b}{c} cb,那么 a c \frac{a}{c} ca的值可以通过$\frac{a}{b}\times \frac{b}{c} $求得。
这个信息给了我一种思路:
- 使用哈希表查找字符串
a
, - 深度优先搜索与
a
相关联的字符串 - 重复步骤2,直到找到字符串
b
数据结构
-
考虑到题目中只给了变量对数组
equations
、实值数组values
和问题数组queries
,要用哈希表查找字符串a
,需要对变量对数组equations
进行一定处理,然后加入到哈希表中。 -
考虑到变量对数组
equations
的每一个元素含有两个字符串,因此使用unordered_map<string,string>
来存储它。 -
考虑到实值数组
values
和变量对数组equations
中的元素是一一对应的。如果只处理变量对数组equations
,在计算的时候查找实值数组values
会很不方便,因此哈希表value
还需要加上double
类型的values[i]
,此时使用的数据结构是unordered_map<string,pair<string,double>>
-
考虑到题目中所给的字符串会和多个字符串相关联,例如 a b \frac{a}{b} ba, a c \frac{a}{c} ca,
a
和b
关联,同时也和c
关联,因此对于同一字符串,会有多个pair<string,double>
,此时的数据结构是unordered_map<string,vector<pair<string,double>>>
-
考虑了这么多,猛地发现这不是一道图论题目吗?我上面的所有工作就是在建图啊。这么一来,这道题目就好做了
思路也很简单:建图 + dfs/bfs
注:建图的方法有很多种,不必局限于我上面的建图思路,比如可以定义自定义一个数据类型。
解题方法
根据上述思路,形成了如下的步骤:
- 用哈希表建图
dfs/bfs
注:和树的dfs/bfs
不同,图的dfs/bfs
需要用一个状态数组标记每一个已经访问过的节点,避免重复访问。
复杂度
-
时间复杂度: O ( M L + Q × ( L + M ) ) O(ML + Q\times (L + M)) O(ML+Q×(L+M))。其中 M M M 为边的数量, Q Q Q 为询问的数量, L L L为字符串的平均长度。构建图时,需要处理 M M M 条边,每条边都涉及到 O ( L ) O(L) O(L) 的字符串比较;处理查询时,每次查询首先要进行一次 O ( L ) O(L) O(L) 的比较,然后至多遍历 M M M 条边。
-
空间复杂度: O ( n ) O(n) O(n)。其中 N N N为点的数量, M M M 为边的数量, L L L 为字符串的平均长度。为了将每个字符串映射到整数,需要开辟空间为 O ( N L ) O(NL) O(NL)的哈希表;随后,需要花费 O ( M ) O(M) O(M)的空间存储每条边的权重;处理查询时,还需要 O ( N ) O(N) O(N) 的空间维护访问队列。最终,总的复杂度为 O ( N L + M + N ) = O ( N L + M ) O(NL+M+N)=O(NL+M) O(NL+M+N)=O(NL+M)。
Code
class Solution {
public:
// 这是一个图论问题
unordered_map<string,vector<pair<string,double>> > hash;
unordered_set<string> st_dfs;
// key : Ai value : Bi values[i]
// 会有重复的Ai
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
vector<double> res;
// 是时候构建一个新哈希表了,然后dfs
for(int i = 0;i < equations.size();++ i){
hash[equations[i][0]].push_back({equations[i][1],values[i]}) ;
if(values[i] != 0){
hash[equations[i][1]].push_back({equations[i][0],1 / values[i]});
}
}
for(int i = 0;i < queries.size();++ i){
st_dfs.clear();
double ans = dfs(queries[i][0],queries[i][1]);
res.push_back(ans);
}
return res;
}
double bfs(string origin,string destination){
unordered_map<string,set<string>> st;
queue<pair<string,double>> q;
q.push({origin,1});
while(!q.empty()){
auto [str,val] = q.front();q.pop();
if(hash.find(str) != hash.end()){ // 存在当前字符串
auto &v = hash[str]; // 取当前字符串对应的信息
for(int i = 0;i < v.size();++ i){// 遍历
if(v[i].first == destination) return val * v[i].second; // 找到了
else{
if(st.find(str) == st.end() || (st[str].find(v[i].first) == st[str].end())){
// 如果没遍历过
st[str].insert(v[i].first);
q.push({v[i].first,val * v[i].second});
}
}
}
}
}
return -1;
}
double dfs(string origin,string destination){
if(hash.find(origin) == hash.end() || hash.find(destination) == hash.end()) return -1;
if(origin == destination) return 1;
st_dfs.insert(origin);
auto &v = hash[origin];
// int ans = -1;
for(int i = 0;i < v.size();++ i){
if(st_dfs.find(v[i].first) == st_dfs.end()){ // 当前节点没有遍历过
double val = dfs(v[i].first,destination);
if(val != -1) return val * v[i].second;
}
}
return -1;
}
};
407.接雨水II
这道题目的本质是求解每个单元的能存水的最高高度,每个单元能存水的最高高度取决于该单元到矩阵边界的每条路径的最大值中的最小值。
433. 最小基因变化
这道题目是一个图论问题,要求求解源点到汇点的最短路。
求解最短路由以下两种常用的方法:
- 如果图的边权为
1
,可以使用BFS
; - 如果图的边权不为
1
,但边权为正,则可以使用Dijkstra
算法求解最短路。
既然是最短路问题,那么本题的做法就很套路了:
- 建图
BFS
/Dijkstra
算法求解最短路