Skip to content

Commit 34d2a5d

Browse files
committed
feat: add solutions to lc problem: No.1971
No.1971.Find if Path Exists in Graph
1 parent bec7ed2 commit 34d2a5d

File tree

2 files changed

+327
-50
lines changed

2 files changed

+327
-50
lines changed

solution/1900-1999/1971.Find if Path Exists in Graph/README.md

Lines changed: 225 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,131 @@
5353

5454
**方法一:DFS**
5555

56+
我们先将 `edges` 转换成邻接表 $g$,然后使用 DFS,判断是否存在从 `source``destination` 的路径。
57+
58+
过程中,我们用数组 `vis` 记录已经访问过的顶点,避免重复访问。
59+
60+
时间复杂度 $O(n + m)$,其中 $n$ 和 $m$ 分别是节点数和边数。
61+
5662
**方法二:并查集**
5763

64+
判断图中两个节点是否连通,一种比较简单直接的方法是使用并查集。
65+
66+
先构建并查集,然后将每条边的两个节点合并。
67+
68+
最后查询 `source``destination` 的祖宗节点是否相同,相同则说明两个节点连通。
69+
70+
时间复杂度 $O(n + m \times \alpha(m))$,空间复杂度 $O(n)$。其中 $n$ 和 $m$ 分别是节点数和边数。
71+
72+
附并查集相关介绍以及常用模板:
73+
74+
并查集是一种树形的数据结构,顾名思义,它用于处理一些不交集的**合并****查询**问题。 它支持两种操作:
75+
76+
1. 查找(Find):确定某个元素处于哪个子集,单次操作时间复杂度 $O(\alpha(n))$
77+
1. 合并(Union):将两个子集合并成一个集合,单次操作时间复杂度 $O(\alpha(n))$
78+
79+
其中 $\alpha$ 为阿克曼函数的反函数,其增长极其缓慢,也就是说其单次操作的平均运行时间可以认为是一个很小的常数。
80+
81+
以下是并查集的常用模板,需要熟练掌握。其中:
82+
83+
- `n` 表示节点数
84+
- `p` 存储每个点的父节点,初始时每个点的父节点都是自己
85+
- `size` 只有当节点是祖宗节点时才有意义,表示祖宗节点所在集合中,点的数量
86+
- `find(x)` 函数用于查找 $x$ 所在集合的祖宗节点
87+
- `union(a, b)` 函数用于合并 $a$ 和 $b$ 所在的集合
88+
89+
```python [sol1-Python3 模板]
90+
p = list(range(n))
91+
size = [1] * n
92+
93+
def find(x):
94+
if p[x] != x:
95+
# 路径压缩
96+
p[x] = find(p[x])
97+
return p[x]
98+
99+
100+
def union(a, b):
101+
pa, pb = find(a), find(b)
102+
if pa == pb:
103+
return
104+
p[pa] = pb
105+
size[pb] += size[pa]
106+
```
107+
108+
```java [sol1-Java 模板]
109+
int[] p = new int[n];
110+
int[] size = new int[n];
111+
for (int i = 0; i < n; ++i) {
112+
p[i] = i;
113+
size[i] = 1;
114+
}
115+
116+
int find(int x) {
117+
if (p[x] != x) {
118+
// 路径压缩
119+
p[x] = find(p[x]);
120+
}
121+
return p[x];
122+
}
123+
124+
void union(int a, int b) {
125+
int pa = find(a), pb = find(b);
126+
if (pa == pb) {
127+
return;
128+
}
129+
p[pa] = pb;
130+
size[pb] += size[pa];
131+
}
132+
```
133+
134+
```cpp [sol1-C++ 模板]
135+
vector<int> p(n);
136+
iota(p.begin(), p.end(), 0);
137+
vector<int> size(n, 1);
138+
139+
int find(int x) {
140+
if (p[x] != x) {
141+
// 路径压缩
142+
p[x] = find(p[x]);
143+
}
144+
return p[x];
145+
}
146+
147+
void unite(int a, int b) {
148+
int pa = find(a), pb = find(b);
149+
if (pa == pb) return;
150+
p[pa] = pb;
151+
size[pb] += size[pa];
152+
}
153+
```
154+
155+
```go [sol1-Go 模板]
156+
p := make([]int, n)
157+
size := make([]int, n)
158+
for i := range p {
159+
p[i] = i
160+
size[i] = 1
161+
}
162+
163+
func find(x int) int {
164+
if p[x] != x {
165+
// 路径压缩
166+
p[x] = find(p[x])
167+
}
168+
return p[x]
169+
}
170+
171+
func union(a, b int) {
172+
pa, pb := find(a), find(b)
173+
if pa == pb {
174+
return
175+
}
176+
p[pa] = pb
177+
size[pb] += size[pa]
178+
}
179+
```
180+
58181
<!-- tabs:start -->
59182

60183
### **Python3**
@@ -63,26 +186,22 @@
63186

64187
```python
65188
class Solution:
66-
def validPath(self, n: int, edges: List[List[int]], start: int, end: int) -> bool:
67-
def dfs(u):
68-
nonlocal ans
69-
if ans or u in vis:
70-
return
71-
vis.add(u)
72-
if u == end:
73-
ans = True
74-
return
75-
for v in g[u]:
76-
dfs(v)
189+
def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
190+
def dfs(i):
191+
if i == destination:
192+
return True
193+
vis.add(i)
194+
for j in g[i]:
195+
if j not in vis and dfs(j):
196+
return True
197+
return False
77198

78199
g = defaultdict(list)
200+
for a, b in edges:
201+
g[a].append(b)
202+
g[b].append(a)
79203
vis = set()
80-
ans = False
81-
for u, v in edges:
82-
g[u].append(v)
83-
g[v].append(u)
84-
dfs(start)
85-
return ans
204+
return dfs(source)
86205
```
87206

88207
```python
@@ -103,6 +222,38 @@ class Solution:
103222

104223
<!-- 这里可写当前语言的特殊实现逻辑 -->
105224

225+
```java
226+
class Solution {
227+
private boolean[] vis;
228+
private List<Integer>[] g;
229+
230+
public boolean validPath(int n, int[][] edges, int source, int destination) {
231+
vis = new boolean[n];
232+
g = new List[n];
233+
Arrays.setAll(g, k -> new ArrayList<>());
234+
for (var e : edges) {
235+
int a = e[0], b = e[1];
236+
g[a].add(b);
237+
g[b].add(a);
238+
}
239+
return dfs(source, destination);
240+
}
241+
242+
private boolean dfs(int source, int destination) {
243+
if (source == destination) {
244+
return true;
245+
}
246+
vis[source] = true;
247+
for (int nxt : g[source]) {
248+
if (!vis[nxt] && dfs(nxt, destination)) {
249+
return true;
250+
}
251+
}
252+
return false;
253+
}
254+
}
255+
```
256+
106257
```java
107258
class Solution {
108259
private int[] p;
@@ -132,24 +283,73 @@ class Solution {
132283
```cpp
133284
class Solution {
134285
public:
135-
vector<int> p;
286+
bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {
287+
vector<bool> vis(n);
288+
vector<vector<int>> g(n);
289+
for (auto& e : edges) {
290+
int a = e[0], b = e[1];
291+
g[a].emplace_back(b);
292+
g[b].emplace_back(a);
293+
}
294+
function<bool(int)> dfs = [&](int i) -> bool {
295+
if (i == destination) return true;
296+
vis[i] = true;
297+
for (int& j : g[i]) {
298+
if (!vis[j] && dfs(j)) {
299+
return true;
300+
}
301+
}
302+
return false;
303+
};
304+
return dfs(source);
305+
}
306+
};
307+
```
136308
309+
```cpp
310+
class Solution {
311+
public:
137312
bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {
138-
p.resize(n);
139-
for (int i = 0; i < n; ++i) p[i] = i;
313+
vector<int> p(n);
314+
iota(p.begin(), p.end(), 0);
315+
function<int(int)> find = [&](int x) -> int {
316+
if (p[x] != x) p[x] = find(p[x]);
317+
return p[x];
318+
};
140319
for (auto& e : edges) p[find(e[0])] = find(e[1]);
141320
return find(source) == find(destination);
142321
}
143-
144-
int find(int x) {
145-
if (p[x] != x) p[x] = find(p[x]);
146-
return p[x];
147-
}
148322
};
149323
```
150324

151325
### **Go**
152326

327+
```go
328+
func validPath(n int, edges [][]int, source int, destination int) bool {
329+
vis := make([]bool, n)
330+
g := make([][]int, n)
331+
for _, e := range edges {
332+
a, b := e[0], e[1]
333+
g[a] = append(g[a], b)
334+
g[b] = append(g[b], a)
335+
}
336+
var dfs func(int) bool
337+
dfs = func(i int) bool {
338+
if i == destination {
339+
return true
340+
}
341+
vis[i] = true
342+
for _, j := range g[i] {
343+
if !vis[j] && dfs(j) {
344+
return true
345+
}
346+
}
347+
return false
348+
}
349+
return dfs(source)
350+
}
351+
```
352+
153353
```go
154354
func validPath(n int, edges [][]int, source int, destination int) bool {
155355
p := make([]int, n)

0 commit comments

Comments
 (0)