class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
if len(edges) != n - 1:
return False
parent = [i for i in range(n)]
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def union(x, y):
rootX, rootY = find(x), find(y)
if rootX == rootY:
return False
parent[rootY] = rootX
return True
for u, v in edges:
if not union(u, v):
return False
return True
class Solution {
public:
bool validTree(int n, vector<vector<int>>& edges) {
if (edges.size() != n - 1) return false;
vector<int> parent(n);
for (int i = 0; i < n; ++i) parent[i] = i;
function<int(int)> find = [&](int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
};
for (auto& edge : edges) {
int u = edge[0], v = edge[1];
int pu = find(u), pv = find(v);
if (pu == pv) return false;
parent[pv] = pu;
}
return true;
}
};
class Solution {
public boolean validTree(int n, int[][] edges) {
if (edges.length != n - 1) return false;
int[] parent = new int[n];
for (int i = 0; i < n; ++i) parent[i] = i;
int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
for (int[] edge : edges) {
int u = edge[0], v = edge[1];
int pu = find(u), pv = find(v);
if (pu == pv) return false;
parent[pv] = pu;
}
return true;
}
}
var validTree = function(n, edges) {
if (edges.length !== n - 1) return false;
let parent = Array(n).fill(0).map((_, i) => i);
function find(x) {
while (parent[x] !== x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
for (let [u, v] of edges) {
let pu = find(u), pv = find(v);
if (pu === pv) return false;
parent[pv] = pu;
}
return true;
};
You are given two inputs: an integer n
representing the number of nodes labeled from 0
to n - 1
, and a list of undirected edges edges
where each edge connects two nodes. Your task is to determine if these edges form a valid tree.
For example, given n = 5
and edges = [[0,1],[0,2],[0,3],[1,4]]
, the function should return true
because the edges form a valid tree.
The problem asks whether the given edges form a valid tree. Let's recall the properties of a tree:
n
nodes, a tree has exactly n - 1
edges.The brute-force way would be to check for cycles and ensure all nodes are connected, perhaps using DFS or BFS for each node. But this can be inefficient for large graphs.
An optimized approach is to use the Union-Find (Disjoint Set Union) data structure, which efficiently detects cycles and helps us determine if the graph is connected as we process each edge.
Here is a step-by-step breakdown of the algorithm:
n - 1
, it's impossible to form a tree (either there are too many edges, creating a cycle, or too few, making the graph disconnected).[u, v]
, try to union their sets.u
and v
already have the same root, a cycle is detected, so return false
.n-1
, and no cycles were found, the graph is connected and acyclic, hence a tree.We use Union-Find because it allows us to efficiently check if two nodes are already connected (detecting cycles) and to merge their sets (ensuring connectivity), both in nearly constant time.
Let's walk through an example with n = 5
and edges = [[0,1],[0,2],[0,3],[1,4]]
:
n-1
(good).parent = [0, 1, 2, 3, 4]
.[0,1]
:
[0,2]
:
[0,3]
:
[1,4]
:
Brute-force approach:
find
and union
operation is nearly O(1) due to path compression.In practice, the Union-Find approach is very efficient for this problem.
To determine if a set of edges forms a valid tree, we check that the number of edges is exactly n-1
and use the Union-Find algorithm to ensure there are no cycles and all nodes are connected. This approach is both intuitive and highly efficient, leveraging the mathematical properties of trees and the power of the Union-Find data structure. The solution is elegant because it reduces a seemingly complex graph problem to a few simple, fast checks.