lcGraph2 - 图的常见题集2
0 图常见算法
并查集就不提了,算连通性的
dijstra,单元点最短路径
tarjan算法:
trajan 塔杨算法 求割点(关键点),割边
1192criticalConnections 查找集群内的「关键连接」
1 题目
https://leetcode.cn/problems/critical-connections-in-a-network/
2 解题思路
- 1 解题思路:使用tarjan算法:求割点和割边
- 算法实现参考:https://www.cnblogs.com/collectionne/p/6847240.html
- 一句话概括算法:dfsNo[i]: i号节点dfs的顺序, low[i]:i号节点以及其子树中所有的搜索节点仅通过 回边 能够到达的节点的dfs序号(回边:非搜索树上的边),若对于边u->v,有:dfsNo[u] < low[v],说明v通过回边无法到达u之前的节点,那么说明uv为割边,必须是小于,因为若果回边能回到u,且dfsNo[u] == low[v],则说明uv不是割边!
- 同理,dfsNo[u] <= low[v]可以得出u为割点,之所以有等于号,是因为u是可以被割的
1 | class Solution { |
tarjan求连通子集
https://byvoid.com/zhs/blog/scc-tarjan/
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
1 题集
0882reachableSubNodes 细分图中的可到达结点 dijstra
1 题目
https://leetcode.cn/problems/reachable-nodes-in-subdivided-graph/
2 解题思路
- 1 解题思路:类似的题目:https://leetcode.cn/problems/partition-array-into-two-arrays-to-minimize-sum-difference/
- 1.1 dijstra算出来到所有顶点的距离
- 1.2 最终能到达的节点由两部分构成:
- a : 来自原始节点,要求距离小于maxMoves
- b : 来自细分节点:很简单,对于每个边uv,从u和v出发,分别还能前进maxMoves - dis[u], maxMoves - dis[v],这就是细分节点的个数
- 需要注意两点:1,maxMoves不一定比dis[u]大;2,若从u和v出发加起来的细分节点数大于uv之间所有的细分节点,记得clamp到uv之间的细分节点个数
- 2 回顾dijstra算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70unordered_map<int, unordered_map<int, int>> g;
auto cmp = [](const pair<int, int>& a, const pair<int, int>& b) {
return a.first > b.first;
};
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp);
for(auto& e : edges) {
int u = e[0];
int v = e[1];
int w = e[2];
g[u][v] = w+1;
g[v][u] = w+1;
}
// init dis
vector<int> disRes(n, INT_MAX);
map<int, bool> visited;
visited[0] = true;
disRes[0] = 0;
pq.push({0, 0});
// contain 0 itSelf!
int res = 0;
// dijstra
while(pq.size() > 0) {
auto node = pq.top();
pq.pop();
int u = node.second;
int dis = node.first;
// for disRes[u], every relax will push a new pair, so we need
// to pass all old relaxed value
/**
*
* eg: [[2,3,4],[1,3,9],[0,2,4],[0,1,1]]
* 对于上图的dijstra的距离表的松弛过程为:
0 2 2147483647 2147483647
0 2 5 2147483647
0 2 5 12 // 这里,第一次松弛来自节点1,
0 2 5 10 // 第二次来自节点2,那么这两个松弛结果都会记录在pq里面,为了让后面
// pq遍历到 0到3距离为12的时候能pass掉,我么你需要将12和 10(存于距离中)做比较,
// 就可以直到12是第一次松弛的结果,10才是最终松弛的结果,否则如果还有其他节点,
// 那么12会在10松弛完其他节点接着松弛,那就错了
// 于是这个continue是必要的
*/
if(disRes[u] < dis) {
continue;
}
visited[u] = true;
disRes[u] = dis;
res += (disRes[u] <= maxMoves);
// relax nodes by u
for(auto& neighbor : g[u]) {
int v = neighbor.first;
int w = neighbor.second;
// v has been proceed!
if(!visited[v] && disRes[v] > disRes[u] + g[u][v]) {
disRes[v] = disRes[u] + g[u][v];
pq.push({disRes[v], v});
// print(disRes);
}
}
};
1 | class Solution { |
1368minCostToHaveAPath 使网格图至少有一条有效路径的最小代价
1 题目
https://leetcode.cn/problems/minimum-cost-to-make-at-least-one-valid-path-in-a-grid/
2 解题思路
- 1 解题思路:
- 1.1 说明思路:构图,正确方向,那么u到v的距离为0,否则为1
- 那么不就求从00到mn的最短路吗?dijstra可以做
- 1.2 但是你非要用bfs:
- 解决办法也简单,此时决定一个点v是否进入下一次bfs不再是vis数组了,而是:用dis[u]表示从0到u的距离,然后它的邻居v们,是否进入下一次bfs,是由:dis[v] < dis[u] + g[u][v].weight,也就是如果v被松弛了,进入下一次bfs,这个和dijstra思想基本上是一样的,但是可能有很多重复计算
- 1.3 这个叫01bfs,也就是说,队列里的点,到原点的距离,要么是dis,要么是dis+1,当然是使用deque维护,g[u][v].weight如果为1,从队尾进入,否则从对首进入,但是不这么做貌似也行哈哈哈,就用普通的队列即可
- 1.1 说明思路:构图,正确方向,那么u到v的距离为0,否则为1
1 | class Solution { |
1579maxNumEdgeToRemove 保证图可完全遍历的最大删除边数
1 题目
https://leetcode.cn/problems/remove-max-number-of-edges-to-keep-graph-fully-traversable/
2 解题思路
- 1 解题思路:(不如说:并查集就是典型的逆向思维)
- 1.1 不要顺着考虑删除边的情况,那样式2^n复杂度,解决不了问题,逆向思维,考虑一条条加入边
- 1.2 肯定是优先加入公共边,若能merge则是有用边,否则为无用边,则++res,然后看是否连通,如果连通,就不用alice和bob的专业边了,可以直接返回结果
- 1.3 不然的话,要分别考虑alice和bob,对alice来说,从上面1.2中得到的并查集,对每一条a边尝试加入,失败则++res,对bob同样如此,最后返回res,当然失败情况就是,alice和bob至少有一个人的并查集的root数目大于1;
1 | class Solution { |
1928minCostToReachInMaxTime 规定时间内到达终点的最小花费
1 题目
https://leetcode.cn/problems/minimum-cost-to-reach-destination-in-time/
2 解题思路
- 1 解题思路:动态规划:
- f[t][i]: fees in exact time t to reach node i
- f[t][i] = min(f[t - time(j, i)][j] + fee[i])
1 | class Solution { |
2065maxPathValueWithinMaxtime 规定时间内路径的最大价值
1 题目
https://leetcode.cn/problems/maximum-path-quality-of-a-graph/
2 解题思路
- 1 解题思路:
- 1.1 考虑一个问题:如何遍历所有路径?那不就是回溯吗,对于u,尝试v进入下一个dfs,然后dfs完成v以后尝试下一个v,将之前的v设置为未访问即可
- 1.2 那么什么时候更新结果?只要v == 0并且剩余时间 >= 0,就可以更新结果
- 1.3 那什么时候返回?时间小于0了就返回啊
- 1.4 有个问题,要求路径中节点的值不被重复加?那不就是回溯过程中,如果当前顶点v被用过,那么直接从v进入dfs,就不去将当前v的价值考虑进去就行,而且也不需要去标记它没被访问过,因为它是已经被访问过的节点(走回头路了嘛),因为只有一个地方可以回溯,那么就是进入v且v此时没有被访问过然后dfs完成以后可以将v置为未访问
1 | class Solution { |