🧠
Brute Force (Cycle Detection by DFS for Each Edge)
💡 This approach exists to build intuition on cycle detection by explicitly checking connectivity before adding each edge. It is slow but conceptually straightforward.
Intuition
For each edge, remove it temporarily and check if the graph remains connected without cycles. If yes, that edge is redundant. This involves checking connectivity repeatedly.
Algorithm
- Initialize an empty graph with n nodes and no edges.
- For each edge in edges:
- Use DFS to check if the two nodes of the edge are already connected in the current graph.
- If connected, return this edge as the redundant connection.
- Otherwise, add the edge to the graph and continue.
💡 The main difficulty is understanding how to check connectivity efficiently and why checking before adding an edge detects cycles.
from collections import defaultdict
def findRedundantConnection(edges):
graph = defaultdict(list)
def dfs(source, target, visited):
if source == target:
return True
visited.add(source)
for neighbor in graph[source]:
if neighbor not in visited:
if dfs(neighbor, target, visited):
return True
return False
for u, v in edges:
visited = set()
if u in graph and v in graph and dfs(u, v, visited):
return [u, v]
graph[u].append(v)
graph[v].append(u)
# Driver code
if __name__ == '__main__':
edges = [[1,2],[1,3],[2,3]]
print(findRedundantConnection(edges)) # Output: [2,3]
Line Notes
graph = defaultdict(list)Initialize adjacency list to represent the graph so we can efficiently track neighbors.
def dfs(source, target, visited):Define DFS to check if target node is reachable from source node, indicating connectivity.
if source == target:Base case: if current node is target, connectivity is confirmed.
visited.add(source)Mark current node as visited to avoid cycles and repeated work.
for neighbor in graph[source]:Explore all neighbors of the current node to continue DFS traversal.
if neighbor not in visited:Only visit neighbors not yet visited to prevent infinite loops.
if dfs(neighbor, target, visited):If any neighbor leads to target, propagate True upwards.
if u in graph and v in graph and dfs(u, v, visited):Check if u and v are already connected before adding the edge, indicating a cycle.
graph[u].append(v)Add edge from u to v to the graph as it does not create a cycle.
graph[v].append(u)Add edge from v to u to maintain undirected graph representation.
import java.util.*;
public class Solution {
public int[] findRedundantConnection(int[][] edges) {
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int[] edge : edges) {
int u = edge[0], v = edge[1];
Set<Integer> visited = new HashSet<>();
if (graph.containsKey(u) && graph.containsKey(v) && dfs(u, v, graph, visited)) {
return edge;
}
graph.computeIfAbsent(u, k -> new ArrayList<>()).add(v);
graph.computeIfAbsent(v, k -> new ArrayList<>()).add(u);
}
return new int[0];
}
private boolean dfs(int source, int target, Map<Integer, List<Integer>> graph, Set<Integer> visited) {
if (source == target) return true;
visited.add(source);
if (!graph.containsKey(source)) return false;
for (int neighbor : graph.get(source)) {
if (!visited.contains(neighbor)) {
if (dfs(neighbor, target, graph, visited)) return true;
}
}
return false;
}
// Driver code
public static void main(String[] args) {
Solution sol = new Solution();
int[][] edges = {{1,2},{1,3},{2,3}};
int[] res = sol.findRedundantConnection(edges);
System.out.println(Arrays.toString(res)); // Output: [2,3]
}
}
Line Notes
Map<Integer, List<Integer>> graph = new HashMap<>();Initialize adjacency list to represent the graph for efficient neighbor lookups.
if (graph.containsKey(u) && graph.containsKey(v) && dfs(u, v, graph, visited)) {Check if u and v are already connected, indicating adding this edge creates a cycle.
graph.computeIfAbsent(u, k -> new ArrayList<>()).add(v);Add edge from u to v to the graph as it does not create a cycle.
graph.computeIfAbsent(v, k -> new ArrayList<>()).add(u);Add edge from v to u to maintain undirected graph representation.
if (source == target) return true;Base case for DFS: found the target node, confirming connectivity.
visited.add(source);Mark current node as visited to avoid revisiting and infinite loops.
if (!graph.containsKey(source)) return false;If no neighbors, cannot reach target from here.
for (int neighbor : graph.get(source)) {Explore all neighbors of current node in DFS.
if (!visited.contains(neighbor)) {Visit only unvisited neighbors to prevent cycles.
if (dfs(neighbor, target, graph, visited)) return true;If any neighbor leads to target, propagate True.
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;
class Solution {
public:
bool dfs(int source, int target, unordered_map<int, vector<int>>& graph, unordered_set<int>& visited) {
if (source == target) return true;
visited.insert(source);
if (graph.find(source) == graph.end()) return false;
for (int neighbor : graph[source]) {
if (visited.find(neighbor) == visited.end()) {
if (dfs(neighbor, target, graph, visited)) return true;
}
}
return false;
}
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
unordered_map<int, vector<int>> graph;
for (auto& edge : edges) {
int u = edge[0], v = edge[1];
unordered_set<int> visited;
if (graph.count(u) && graph.count(v) && dfs(u, v, graph, visited)) {
return edge;
}
graph[u].push_back(v);
graph[v].push_back(u);
}
return {};
}
};
int main() {
Solution sol;
vector<vector<int>> edges = {{1,2},{1,3},{2,3}};
vector<int> res = sol.findRedundantConnection(edges);
cout << "[" << res[0] << "," << res[1] << "]" << endl; // Output: [2,3]
return 0;
}
Line Notes
unordered_map<int, vector<int>> graph;Adjacency list to represent the graph for efficient neighbor access.
if (graph.count(u) && graph.count(v) && dfs(u, v, graph, visited)) {Check if u and v are already connected, indicating a cycle if edge added.
graph[u].push_back(v);Add edge from u to v as it does not create a cycle.
graph[v].push_back(u);Add edge from v to u to maintain undirected graph.
if (source == target) return true;DFS base case: found target node confirming connectivity.
visited.insert(source);Mark current node as visited to avoid revisiting.
if (graph.find(source) == graph.end()) return false;If no neighbors, cannot reach target from here.
for (int neighbor : graph[source]) {Explore neighbors of current node in DFS.
if (visited.find(neighbor) == visited.end()) {Visit only unvisited neighbors to prevent cycles.
if (dfs(neighbor, target, graph, visited)) return true;If neighbor leads to target, propagate True.
function findRedundantConnection(edges) {
const graph = new Map();
function dfs(source, target, visited) {
if (source === target) return true;
visited.add(source);
if (!graph.has(source)) return false;
for (const neighbor of graph.get(source)) {
if (!visited.has(neighbor)) {
if (dfs(neighbor, target, visited)) return true;
}
}
return false;
}
for (const [u, v] of edges) {
const visited = new Set();
if (graph.has(u) && graph.has(v) && dfs(u, v, visited)) {
return [u, v];
}
if (!graph.has(u)) graph.set(u, []);
if (!graph.has(v)) graph.set(v, []);
graph.get(u).push(v);
graph.get(v).push(u);
}
}
// Test
console.log(findRedundantConnection([[1,2],[1,3],[2,3]])); // Output: [2,3]
Line Notes
const graph = new Map();Initialize adjacency list as a Map for efficient neighbor lookups.
function dfs(source, target, visited) {DFS helper to check if target is reachable from source.
if (source === target) return true;Base case: found target node confirming connectivity.
visited.add(source);Mark current node as visited to avoid revisiting.
if (!graph.has(source)) return false;If no neighbors, cannot reach target from here.
for (const neighbor of graph.get(source)) {Explore neighbors of current node in DFS.
if (!visited.has(neighbor)) {Visit only unvisited neighbors to prevent cycles.
if (dfs(neighbor, target, visited)) return true;If neighbor leads to target, propagate True.
if (graph.has(u) && graph.has(v) && dfs(u, v, visited)) {Check if u and v are already connected before adding edge.
graph.get(u).push(v);Add edge from u to v as it does not create a cycle.
graph.get(v).push(u);Add edge from v to u to maintain undirected graph.
For each edge, DFS can take O(n) in worst case, repeated for n edges → O(n^2). Space is O(n) for graph and visited set.
💡 For n=1000, this means up to 1,000,000 operations, which is slow for large inputs.
Interview Verdict: TLE / Use only to introduce
This approach is too slow for large inputs but helps understand cycle detection and connectivity checks.