🧠
Brute Force (DFS Cycle Detection + Connectivity Check)
💡 Starting with brute force helps understand the fundamental graph properties: connectivity and cycle detection. It uses basic DFS traversal without optimizations.
Intuition
If the graph is connected and has no cycles, it is a valid tree. We can check connectivity by DFS from node 0 and detect cycles by tracking visited nodes and parent relationships.
Algorithm
- Build adjacency list from edges.
- Initialize a visited set to track visited nodes.
- Perform DFS from node 0, passing parent node to detect cycles.
- If a cycle is detected during DFS, return false.
- After DFS, check if all nodes are visited to ensure connectivity.
- Return true if no cycles and graph is connected.
💡 The challenge is to detect cycles correctly by remembering the parent node to avoid false positives and ensuring all nodes are visited.
from collections import defaultdict
def valid_tree(n, edges):
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
visited = set()
def dfs(node, parent):
visited.add(node)
for neighbor in graph[node]:
if neighbor == parent:
continue
if neighbor in visited or not dfs(neighbor, node):
return False
return True
if not dfs(0, -1):
return False
return len(visited) == n
# Driver code
if __name__ == '__main__':
print(valid_tree(5, [[0,1],[0,2],[0,3],[1,4]])) # True
print(valid_tree(5, [[0,1],[1,2],[2,3],[1,3],[1,4]])) # False
Line Notes
graph = defaultdict(list)Build adjacency list for efficient neighbor lookup
for u, v in edgesAdd edges bidirectionally since graph is undirected
visited = set()Track nodes visited during DFS to detect cycles and connectivity
def dfs(node, parent)DFS function with parent to avoid false cycle detection
if neighbor == parentSkip the edge leading back to parent node
if neighbor in visited or not dfs(neighbor, node)Detect cycle if neighbor already visited or DFS fails
if not dfs(0, -1)Start DFS from node 0, no parent (-1)
return len(visited) == nCheck if all nodes are visited to confirm connectivity
import java.util.*;
public class Solution {
public boolean validTree(int n, int[][] edges) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
boolean[] visited = new boolean[n];
if (!dfs(0, -1, graph, visited)) return false;
for (boolean v : visited) if (!v) return false;
return true;
}
private boolean dfs(int node, int parent, List<List<Integer>> graph, boolean[] visited) {
visited[node] = true;
for (int neighbor : graph.get(node)) {
if (neighbor == parent) continue;
if (visited[neighbor] || !dfs(neighbor, node, graph, visited)) return false;
}
return true;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.validTree(5, new int[][]{{0,1},{0,2},{0,3},{1,4}})); // true
System.out.println(sol.validTree(5, new int[][]{{0,1},{1,2},{2,3},{1,3},{1,4}})); // false
}
}
Line Notes
List<List<Integer>> graph = new ArrayList<>()Initialize adjacency list for graph representation
for (int[] edge : edges)Add edges bidirectionally for undirected graph
boolean[] visited = new boolean[n]Track visited nodes during DFS
if (!dfs(0, -1, graph, visited))Start DFS from node 0 with no parent
if (neighbor == parent) continueAvoid revisiting the parent node to prevent false cycle detection
if (visited[neighbor] || !dfs(neighbor, node, graph, visited))Detect cycle if neighbor visited or DFS fails
for (boolean v : visited) if (!v) return falseCheck connectivity by ensuring all nodes visited
return trueReturn true if no cycles and graph is connected
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
bool validTree(int n, vector<vector<int>>& edges) {
vector<vector<int>> graph(n);
for (auto& edge : edges) {
graph[edge[0]].push_back(edge[1]);
graph[edge[1]].push_back(edge[0]);
}
vector<bool> visited(n, false);
if (!dfs(0, -1, graph, visited)) return false;
for (bool v : visited) if (!v) return false;
return true;
}
private:
bool dfs(int node, int parent, vector<vector<int>>& graph, vector<bool>& visited) {
visited[node] = true;
for (int neighbor : graph[node]) {
if (neighbor == parent) continue;
if (visited[neighbor] || !dfs(neighbor, node, graph, visited)) return false;
}
return true;
}
};
int main() {
Solution sol;
vector<vector<int>> edges1 = {{0,1},{0,2},{0,3},{1,4}};
cout << boolalpha << sol.validTree(5, edges1) << endl; // true
vector<vector<int>> edges2 = {{0,1},{1,2},{2,3},{1,3},{1,4}};
cout << boolalpha << sol.validTree(5, edges2) << endl; // false
return 0;
}
Line Notes
vector<vector<int>> graph(n)Create adjacency list for graph
for (auto& edge : edges)Add edges bidirectionally for undirected graph
vector<bool> visited(n, false)Track visited nodes during DFS
if (!dfs(0, -1, graph, visited))Start DFS from node 0 with no parent
if (neighbor == parent) continueSkip parent node to avoid false cycle detection
if (visited[neighbor] || !dfs(neighbor, node, graph, visited))Detect cycle if neighbor visited or DFS fails
for (bool v : visited) if (!v) return falseCheck if all nodes visited to confirm connectivity
return trueReturn true if graph is connected and acyclic
function validTree(n, edges) {
const graph = new Array(n).fill(0).map(() => []);
for (const [u, v] of edges) {
graph[u].push(v);
graph[v].push(u);
}
const visited = new Set();
function dfs(node, parent) {
visited.add(node);
for (const neighbor of graph[node]) {
if (neighbor === parent) continue;
if (visited.has(neighbor) || !dfs(neighbor, node)) return false;
}
return true;
}
if (!dfs(0, -1)) return false;
return visited.size === n;
}
// Test cases
console.log(validTree(5, [[0,1],[0,2],[0,3],[1,4]])); // true
console.log(validTree(5, [[0,1],[1,2],[2,3],[1,3],[1,4]])); // false
Line Notes
const graph = new Array(n).fill(0).map(() => [])Initialize adjacency list for graph
for (const [u, v] of edges)Add edges bidirectionally for undirected graph
const visited = new Set()Track visited nodes during DFS
function dfs(node, parent)DFS function with parent to detect cycles
if (neighbor === parent) continueSkip edge back to parent to avoid false cycle detection
if (visited.has(neighbor) || !dfs(neighbor, node))Detect cycle if neighbor visited or DFS fails
if (!dfs(0, -1)) return falseStart DFS from node 0 with no parent
return visited.size === nCheck if all nodes are visited to confirm connectivity
Building adjacency list takes O(n). DFS visits each node and edge once, total O(n). Visited set and recursion stack use O(n) space.
💡 For n=1000, this means roughly 1000 operations, which is efficient enough for interviews.
Interview Verdict: Accepted
This approach is simple and efficient enough for the problem constraints, making it a solid baseline solution.