You are given n
nodes labeled from 0
to n - 1
, and a list of undirected edges represented as pairs of node indices. Your task is to determine whether these edges, along with the nodes, form a valid tree. A valid tree must satisfy two conditions:
For example, consider the following scenarios:
Example 1:
n = 5
edges = [[0, 1], [0, 2], [0, 3], [1, 4]]
Example 2:
n = 5
edges = [[0, 1], [0, 2], [0, 3], [1, 4], [2, 4]]
[2, 4]
creates a cycle.Example 3:
n = 5
edges = [[0, 1], [2, 3]]
0
and 1
are connected, and nodes 2
and 3
are connected, but there's no path between {0, 1}
and {2, 3}
, and node 4
is isolated.Write a function that takes the number of nodes n
and the list of edges edges
as input and returns true
if the graph forms a valid tree, and false
otherwise. Consider edge cases such as empty graphs, single-node graphs, and graphs with disconnected components.
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
We want to determine if a given set of connections represents a valid tree. A brute force approach would explore all possible paths and arrangements to verify the tree properties. We'll check for cycles and ensure all nodes are connected.
Here's how the algorithm would work step-by-step:
def is_valid_tree_brute_force(number_of_nodes, edges):
if not edges and number_of_nodes == 1:
return True
if not edges and number_of_nodes != 1:
return False
# We'll try each node as a root.
for start_node in range(number_of_nodes):
visited_nodes = set()
stack = [(start_node, -1)] # Node, parent
while stack:
node, parent = stack.pop()
if node in visited_nodes:
# Cycle detected, not a valid tree.
break
visited_nodes.add(node)
for neighbor_one, neighbor_two in edges:
if neighbor_one == node:
neighbor = neighbor_two
elif neighbor_two == node:
neighbor = neighbor_one
else:
continue
if neighbor != parent:
stack.append((neighbor, node))
# Check if we visited all nodes and if there are no cycles.
if len(visited_nodes) == number_of_nodes and node not in visited_nodes:
return True
return False
We want to determine if a given set of connections between points forms a valid tree. A valid tree must have all points connected and have no closed loops or cycles. We can verify this using a technique that explores the connections in a structured way.
Here's how the algorithm would work step-by-step:
def is_graph_a_valid_tree(number_of_nodes, edges):
if len(edges) != number_of_nodes - 1:
return False
graph = {i: [] for i in range(number_of_nodes)}
for node1, node2 in edges:
graph[node1].append(node2)
graph[node2].append(node1)
visited_nodes = [False] * number_of_nodes
# Start DFS from node 0.
if not depth_first_search(graph, 0, -1, visited_nodes):
return False
# Check if all nodes are visited.
# This ensures the graph is connected.
for node_visited in visited_nodes:
if not node_visited:
return False
return True
def depth_first_search(graph, current_node, parent_node, visited_nodes):
visited_nodes[current_node] = True
for neighbor_node in graph[current_node]:
if neighbor_node == parent_node:
continue
# Cycle detected!
if visited_nodes[neighbor_node]:
return False
if not depth_first_search(graph, neighbor_node, current_node, visited_nodes):
return False
return True
Case | How to Handle |
---|---|
Null or empty input: n = 0, edges = null | Return true; an empty graph is considered a valid tree. |
Single node: n = 1, edges = [] | Return true; a single node is considered a valid tree. |
Disconnected graph: n > 1, but not all nodes are connected through edges | The number of connected components should be exactly 1, otherwise return false. |
Graph contains a cycle: More edges than nodes-1 | Return false; a tree cannot contain cycles. |
Self-loop: An edge connects a node to itself (e.g., [0, 0]) | The union-find or DFS approach should handle this correctly by detecting the cycle. |
Parallel edges: Multiple edges connect the same two nodes (e.g., [0, 1], [0, 1]) | The cycle detection algorithms should handle this as a cycle, returning false. |
Large number of nodes and edges: n is very large (e.g., n = 10000) | Union-find should provide near linear time complexity, scaling efficiently. |
Input with negative node values | Since the problem is about connectivity, re-map the node values to positive indices starting from 0. |