You are given a graph represented by n
nodes labeled from 0
to n - 1
, and a list of undirected edges
where each edge is a pair of node labels. Determine whether the given graph forms a valid tree.
A valid tree must satisfy the following two conditions:
For example:
n = 5
, edges = [[0,1], [0,2], [0,3], [1,4]]
. The expected output is true
because the graph is connected and contains no cycles.n = 5
, edges = [[0,1], [1,2], [2,3], [1,3], [1,4]]
. The expected output is false
because the graph contains a cycle (1-2-3-1).n = 5
, edges = [[0,1], [0,2], [0,3], [1,2]]
. The expected output is false
because the graph contains a cycle (0-1-2-0).n = 5
, edges = [[0,1], [2,3], [3,4]]
. The expected output is false
because the graph is disconnected.Explain your approach, considering time and space complexity. Also, think about edge cases such as an empty graph, a single-node graph, disconnected graphs, and graphs with cycles. Provide code implementation in Python.
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. |