The problem
You have a graph of n
nodes labeled from 0
to n - 1
. You are given an integer n
and a list of edges
where edges[i] = [ai, bi]
indicates that there is an undirected edge between nodes ai
and bi
in the graph.
Return true
if the edges of the given graph make up a valid tree, and false
otherwise.
Examples
Input: n = 5, edges = [[0,1],[0,2],[0,3],[1,4]]
Output: true
Input: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]]
Output: false
Constraints
- 1 <= n <= 2000
- 0 <= edges.length <= 5000
- edges[i].length == 2
- 0 <= ai, bi < n
- ai != bi
- There are no self-loops or repeated edges.
Depth First Search Solution
func validTree(_ n: Int, _ edges: [[Int]]) -> Bool {
if n == 0 {
return true
}
var adj: [Int: [Int]] = [:]
for i in 0 ..< n {
adj[i] = []
}
for value in edges {
let u = value[0]
let v = value[1]
adj[u]!.append(v)
adj[v]!.append(u)
}
var visit: Set<Int> = []
func dfs(_ i: Int, _ prev: Int) -> Bool {
if visit.contains(i) {
return false
}
visit.insert(i)
for j in adj[i]! {
if j == prev {
continue
}
if !dfs(j, i) {
return false
}
}
return true
}
return dfs(0, -1) && n == visit.count
}
Explanation
From reading the description, we learn that we need to validate a graph tree and check if it is valid. However, the description does not define what a valid tree is. Let’s start there.
- A valid tree cannot have loops (cycles), for example:
- A tree needs to be connected; if it is not, it is an invalid tree, for example:
- An empty graph counts as a valid tree.
We take the input nodes and edges and check if they are connected and if they do not form a loop (cycle). To do that, we need to create an adjacency list for every single input node.
We use the DFS algorithm and visit the neighbors of every single node recursively. If no cycle is detected and the length of the visit
set equals n
, this means the graph is a valid tree, and we return true
; otherwise, we return false
. To avoid false positive results when backtracking, we add a prev
property that tracks the previous value, compares it to the current index, and skips it if they are equal.
Time/Space Complexity
- Time complexity: O(V + E)
- Space complexity: O(V + E)
- Where
V
is the number of vertices andE
is the number of edges.
Breadth First Search Solution
func validTree(_ n: Int, _ edges: [[Int]]) -> Bool {
if n == 0 {
return true
}
var adj: [Int: [Int]] = [:]
for i in 0 ..< n {
adj[i] = []
}
for value in edges {
let u = value[0]
let v = value[1]
adj[u]!.append(v)
adj[v]!.append(u)
}
var visit: Set<Int> = []
var q: [(Int, Int)] = [(0, -1)]
visit.insert(0)
while !q.isEmpty {
let value = q.removeFirst()
let node = value.0
let parent = value.1
for nei in adj[node]! {
if nei == parent {
continue
}
if visit.contains(nei) {
return false
}
visit.insert(nei)
q.append((nei, node))
}
}
return visit.count == n
}
Explanation
We can also solve this problem using the Breadth First Search algorithm. Instead of recursion, we use iteration. The logic behind the solution remains the same: we create an adjacency list, iterate over every single node and its neighbors, and update our visit
set.
Time/Space Complexity
- Time complexity: O(V + E)
- Space complexity: O(V + E)
- Where
V
is the number of vertices andE
is the number of edges.