题目描述
给定编号从 0
到 n - 1
的 n
个结点。给定一个整数 n
和一个 edges
列表,其中 edges[i] = [ai, bi]
表示图中节点 ai
和 bi
之间存在一条无向边。
如果这些边能够形成一个合法有效的树结构,则返回 true
,否则返回 false
。
示例 1:
输入: n = 5, edges = [[0,1],[0,2],[0,3],[1,4]]
输出: true
示例 2:
输入: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]
输出: false
提示:
1 <= n <= 2000
0 <= edges.length <= 5000
edges[i].length == 2
0 <= ai, bi < n
ai != bi
- 不存在自循环或重复的边
解法
方法一:并查集
判断是否是树,需要满足以下两个条件:
- 边的数量等于节点数减一;
- 不存在环。
我们可以使用并查集来判断是否存在环。遍历边,如果两个节点已经在同一个集合中,说明存在环。否则,我们将两个节点合并到同一个集合中。然后将连通分量的数量减一,最后判断连通分量的数量是否为 $1$。
时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 是节点数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
def find(x: int) -> int:
if p[x] != x:
p[x] = find(p[x])
return p[x]
p = list(range(n))
for a, b in edges:
pa, pb = find(a), find(b)
if pa == pb:
return False
p[pa] = pb
n -= 1
return n == 1
|
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 | class Solution {
private int[] p;
public boolean validTree(int n, int[][] edges) {
p = new int[n];
for (int i = 0; i < n; ++i) {
p[i] = i;
}
for (var e : edges) {
int pa = find(e[0]), pb = find(e[1]);
if (pa == pb) {
return false;
}
p[pa] = pb;
--n;
}
return n == 1;
}
private int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | class Solution {
public:
bool validTree(int n, vector<vector<int>>& edges) {
vector<int> p(n);
iota(p.begin(), p.end(), 0);
function<int(int)> find = [&](int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
};
for (auto& e : edges) {
int pa = find(e[0]), pb = find(e[1]);
if (pa == pb) {
return false;
}
p[pa] = pb;
--n;
}
return n == 1;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | func validTree(n int, edges [][]int) bool {
p := make([]int, n)
for i := range p {
p[i] = i
}
var find func(int) int
find = func(x int) int {
if p[x] != x {
p[x] = find(p[x])
}
return p[x]
}
for _, e := range edges {
pa, pb := find(e[0]), find(e[1])
if pa == pb {
return false
}
p[pa] = pb
n--
}
return n == 1
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | /**
* @param {number} n
* @param {number[][]} edges
* @return {boolean}
*/
var validTree = function (n, edges) {
const p = Array.from({ length: n }, (_, i) => i);
const find = x => {
if (p[x] !== x) {
p[x] = find(p[x]);
}
return p[x];
};
for (const [a, b] of edges) {
const pa = find(a);
const pb = find(b);
if (pa === pb) {
return false;
}
p[pa] = pb;
--n;
}
return n === 1;
};
|
方法二:DFS
我们也可以使用深度优先搜索来判断是否存在环。我们可以使用一个数组 $vis$ 来记录访问过的节点,搜索时,我们先将节点标记为已访问,然后遍历与该节点相邻的节点,如果相邻节点已经访问过,则跳过,否则递归访问相邻节点。最后,我们判断是否所有节点都被访问过,如果有未访问过的节点,说明无法构成树,返回 false
。
时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 是节点数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
def dfs(i: int):
vis.add(i)
for j in g[i]:
if j not in vis:
dfs(j)
if len(edges) != n - 1:
return False
g = [[] for _ in range(n)]
for a, b in edges:
g[a].append(b)
g[b].append(a)
vis = set()
dfs(0)
return len(vis) == n
|
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 | class Solution {
private List<Integer>[] g;
private Set<Integer> vis = new HashSet<>();
public boolean validTree(int n, int[][] edges) {
if (edges.length != n - 1) {
return false;
}
g = new List[n];
Arrays.setAll(g, k -> new ArrayList<>());
for (var e : edges) {
int a = e[0], b = e[1];
g[a].add(b);
g[b].add(a);
}
dfs(0);
return vis.size() == n;
}
private void dfs(int i) {
vis.add(i);
for (int j : g[i]) {
if (!vis.contains(j)) {
dfs(j);
}
}
}
}
|
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 | class Solution {
public:
bool validTree(int n, vector<vector<int>>& edges) {
if (edges.size() != n - 1) {
return false;
}
vector<int> g[n];
vector<int> vis(n);
function<void(int)> dfs = [&](int i) {
vis[i] = true;
--n;
for (int j : g[i]) {
if (!vis[j]) {
dfs(j);
}
}
};
for (auto& e : edges) {
int a = e[0], b = e[1];
g[a].push_back(b);
g[b].push_back(a);
}
dfs(0);
return n == 0;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | func validTree(n int, edges [][]int) bool {
if len(edges) != n-1 {
return false
}
g := make([][]int, n)
vis := make([]bool, n)
for _, e := range edges {
a, b := e[0], e[1]
g[a] = append(g[a], b)
g[b] = append(g[b], a)
}
var dfs func(int)
dfs = func(i int) {
vis[i] = true
n--
for _, j := range g[i] {
if !vis[j] {
dfs(j)
}
}
}
dfs(0)
return n == 0
}
|
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 | /**
* @param {number} n
* @param {number[][]} edges
* @return {boolean}
*/
var validTree = function (n, edges) {
if (edges.length !== n - 1) {
return false;
}
const g = Array.from({ length: n }, () => []);
const vis = Array.from({ length: n }, () => false);
for (const [a, b] of edges) {
g[a].push(b);
g[b].push(a);
}
const dfs = i => {
vis[i] = true;
--n;
for (const j of g[i]) {
if (!vis[j]) {
dfs(j);
}
}
};
dfs(0);
return n === 0;
};
|