Does the Road Network Contain a Loop

Solve this Problem
Medium20–25 min
Topics
Companies

You are given the road network of a region as an adjacency list of an undirected graph. Decide whether there is a loop: a closed route through at least three towns that does not use the same road twice.

A depth-first search that remembers where it came from finds a loop the moment it meets an already visited town by a different road.

Test Case 1:

Input:graph = [[1],[0,2,3],[1,3],[1,2],[5],[4],[]]
Output:true
Explanation:Adjacency matrix rows for 0-1, 1-2, 2-3, 1-3, 4-5: 0:[0,1,0,0,0,0,0], 1:[1,0,1,1,0,0,0], 2:[0,1,0,1,0,0,0], 3:[0,1,1,0,0,0,0], 4:[0,0,0,0,0,1,0], 5:[0,0,0,0,1,0,0], 6:all 0. The nodes 1, 2, 3 form a loop.

Test Case 2:

Input:graph = [[1],[0,2],[1]]
Output:false
Explanation:A simple chain 0-1-2 has no loop.

Test Case 3:

Input:graph = [[1],[0],[3],[2]]
Output:false
Explanation:Two separate single edges: still no loop.

Constraints

  • ◆1 ≤ n ≤ 10 nodes numbered 0 … n-1; graph[u] lists, in increasing order, every neighbour of u (adjacency-list form of an undirected graph)
  • ◆If v is in graph[u] then u is in graph[v]; there are no self-loops and no repeated edges
  • ◆The graph may be disconnected
  • ◆A loop is a path that starts and ends at the same node, uses at least 3 nodes and never repeats an edge; return true if the graph contains one
🚀

Try the Dry Run

Don't just read the solution — watch it execute, one step at a time.

Approach & Solutions

Brute Force — Remove Each Road and See if Its Ends Stay Connected

Brute

A road lies on a loop exactly when its two ends are still connected after that road is taken away (the other route is the rest of the loop). So for every road (u, v), run a breadth-first search from u that is not allowed to use that road, and see whether it reaches v. If it does for any road, the graph has a loop. E roads, each costing one search: O(E · (n + E)).

TimeO(E · (n + E))
SpaceO(n)
1class Solution { 2 private boolean connectedWithout(int[][] graph, int a, int b) { 3 boolean[] seen = new boolean[graph.length]; 4 Deque<Integer> queue = new ArrayDeque<>(); 5 seen[a] = true; 6 queue.add(a); 7 while (!queue.isEmpty()) { 8 int u = queue.poll(); 9 if (u == b) return true; 10 for (int w : graph[u]) { 11 if (u == a && w == b) continue; 12 if (!seen[w]) { 13 seen[w] = true; 14 queue.add(w); 15 } 16 } 17 } 18 return false; 19 } 20 21 public boolean hasLoop(int[][] graph) { 22 for (int u = 0; u < graph.length; u++) { 23 for (int v : graph[u]) { 24 if (u < v && connectedWithout(graph, u, v)) return true; 25 } 26 } 27 return false; 28 } 29}

Optimal — Depth-First Search Remembering the Parent

Optimal

Explore with a depth-first search and remember the node you came from (the parent). Every neighbour is either the parent (just the road you arrived by, ignore it), an unvisited node (go deeper), or a node that was ALREADY visited and is not the parent. The last case means there are two different ways to reach it, which is exactly a loop. Do this from every unvisited node to cover all separate groups. Each road is seen twice: O(n + E).

TimeO(n + E)
SpaceO(n)
1class Solution { 2 private boolean explore(int[][] graph, int node, int parent, boolean[] seen) { 3 seen[node] = true; 4 for (int next : graph[node]) { 5 if (next == parent) continue; 6 if (seen[next]) return true; 7 if (explore(graph, next, node, seen)) return true; 8 } 9 return false; 10 } 11 12 public boolean hasLoop(int[][] graph) { 13 boolean[] seen = new boolean[graph.length]; 14 for (int start = 0; start < graph.length; start++) { 15 if (!seen[start] && explore(graph, start, -1, seen)) return true; 16 } 17 return false; 18 } 19}

Related Problems