Facebook Pixel

2077. Paths in Maze That Lead to Same Room πŸ”’

Problem Description

You are given a maze with n rooms numbered from 1 to n. Some rooms are connected by corridors, and these connections are bidirectional (you can go both ways through a corridor).

The input provides:

  • An integer n representing the number of rooms
  • A 2D array corridors where each element [room1, room2] represents a corridor connecting room1 and room2

Your task is to find the confusion score of the maze, which is defined as the number of distinct cycles of exactly length 3.

A cycle of length 3 means:

  • Starting from a room, visiting exactly 2 other rooms, and returning to the starting room
  • For example: 1 β†’ 2 β†’ 3 β†’ 1 is a valid cycle of length 3
  • But 1 β†’ 2 β†’ 3 β†’ 4 (doesn't return to start) and 1 β†’ 2 β†’ 3 β†’ 2 β†’ 1 (length 4, not 3) are not valid

Two cycles are considered different if at least one room in the first cycle is not present in the second cycle.

The solution builds an adjacency list representation of the graph where g[i] contains all rooms directly connected to room i. For each room i, it examines all pairs of its neighbors (j, k). If rooms j and k are also connected to each other, then rooms i, j, and k form a triangle (cycle of length 3). Since each triangle is counted 3 times (once for each vertex as the starting point), the final count is divided by 3.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To find cycles of length 3, we need to identify triangles in the graph. A triangle exists when three rooms are all connected to each other - room A connects to B, B connects to C, and C connects back to A.

The key insight is that if we're standing at any room in a triangle, we can see the other two rooms as direct neighbors. Moreover, those two neighbors must also be connected to each other to complete the triangle.

This leads us to a straightforward approach: for each room, look at all pairs of its neighbors. If two neighbors are also connected to each other, we've found a triangle.

For example, if room 1 is connected to rooms 2 and 3, and rooms 2 and 3 are also connected to each other, then rooms 1, 2, and 3 form a triangle.

However, there's a counting issue to consider. Each triangle will be discovered three times - once when we examine it from each of its three vertices. When we're at room 1, we'll find the triangle (1, 2, 3). When we're at room 2, we'll find the same triangle as (2, 1, 3). And when we're at room 3, we'll find it as (3, 1, 2). These all represent the same cycle.

Therefore, after counting all triangles from every vertex's perspective, we need to divide the total count by 3 to get the actual number of unique triangles.

The solution uses combinations(g[i], 2) to efficiently generate all pairs of neighbors for each room i, then checks if those two neighbors are connected using if j in g[k]. This approach ensures we systematically find all triangles while avoiding redundant checks.

Learn more about Graph patterns.

Solution Approach

The solution uses a graph-based approach with an adjacency list representation to efficiently find all triangles in the maze.

Step 1: Build the Graph

g = defaultdict(set)
for a, b in corridors:
    g[a].add(b)
    g[b].add(a)

We create an adjacency list using a defaultdict(set) where each room maps to a set of its neighboring rooms. For each corridor [a, b], we add b to the neighbor set of a and vice versa, since corridors are bidirectional. Using sets ensures O(1) lookup time when checking if two rooms are connected.

Step 2: Find All Triangles

ans = 0
for i in range(1, n + 1):
    for j, k in combinations(g[i], 2):
        if j in g[k]:
            ans += 1

We iterate through each room i from 1 to n. For each room, we examine all possible pairs of its neighbors using combinations(g[i], 2). This generates all 2-element combinations from room i's neighbor set.

For each pair (j, k) of neighbors:

  • We check if j is in g[k] (equivalently, if k is in g[j] since the graph is undirected)
  • If true, rooms i, j, and k form a triangle

Step 3: Adjust for Overcounting

return ans // 3

Since each triangle is counted exactly 3 times (once from the perspective of each of its three vertices), we divide the total count by 3 to get the actual number of unique triangles.

Time Complexity: O(n Γ— dΒ²) where d is the maximum degree of any vertex, as we check all pairs of neighbors for each vertex.

Space Complexity: O(E) where E is the number of corridors, for storing the adjacency list.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with n = 4 rooms and corridors = [[1,2], [2,3], [3,1], [1,4], [2,4]].

Step 1: Build the adjacency list

After processing all corridors:
g[1] = {2, 3, 4}
g[2] = {1, 3, 4}
g[3] = {1, 2}
g[4] = {1, 2}

Step 2: Find triangles from each room's perspective

From room 1 (neighbors: 2, 3, 4):

  • Check pair (2, 3): Is 2 in g[3]? Yes! β†’ Triangle found: 1-2-3
  • Check pair (2, 4): Is 2 in g[4]? Yes! β†’ Triangle found: 1-2-4
  • Check pair (3, 4): Is 3 in g[4]? No β†’ No triangle
  • Count from room 1: 2

From room 2 (neighbors: 1, 3, 4):

  • Check pair (1, 3): Is 1 in g[3]? Yes! β†’ Triangle found: 2-1-3 (same as 1-2-3)
  • Check pair (1, 4): Is 1 in g[4]? Yes! β†’ Triangle found: 2-1-4 (same as 1-2-4)
  • Check pair (3, 4): Is 3 in g[4]? No β†’ No triangle
  • Count from room 2: 2

From room 3 (neighbors: 1, 2):

  • Check pair (1, 2): Is 1 in g[2]? Yes! β†’ Triangle found: 3-1-2 (same as 1-2-3)
  • Count from room 3: 1

From room 4 (neighbors: 1, 2):

  • Check pair (1, 2): Is 1 in g[2]? Yes! β†’ Triangle found: 4-1-2 (same as 1-2-4)
  • Count from room 4: 1

Step 3: Adjust for overcounting

  • Total count: 2 + 2 + 1 + 1 = 6
  • Each triangle was counted 3 times (once from each vertex)
  • Actual number of triangles: 6 Γ· 3 = 2

The two unique triangles are:

  1. Rooms 1-2-3
  2. Rooms 1-2-4

Therefore, the confusion score is 2.

Solution Implementation

1from collections import defaultdict
2from itertools import combinations
3from typing import List
4
5class Solution:
6    def numberOfPaths(self, n: int, corridors: List[List[int]]) -> int:
7        # Build adjacency list representation of the graph
8        graph = defaultdict(set)
9        for room_a, room_b in corridors:
10            graph[room_a].add(room_b)
11            graph[room_b].add(room_a)
12      
13        # Count the number of cycles of length 3 (triangles)
14        triangle_count = 0
15      
16        # Iterate through each room as a potential vertex in a triangle
17        for room_i in range(1, n + 1):
18            # Check all pairs of neighbors of room_i
19            for room_j, room_k in combinations(graph[room_i], 2):
20                # If room_j and room_k are connected, we have a triangle
21                if room_j in graph[room_k]:
22                    triangle_count += 1
23      
24        # Each triangle is counted 3 times (once for each vertex)
25        # So divide by 3 to get the actual number of triangles
26        return triangle_count // 3
27
1class Solution {
2    public int numberOfPaths(int n, int[][] corridors) {
3        // Create an adjacency list to represent the graph
4        // Each node will have a set of its connected nodes
5        Set<Integer>[] graph = new Set[n + 1];
6        for (int i = 0; i <= n; i++) {
7            graph[i] = new HashSet<>();
8        }
9      
10        // Build the undirected graph from corridors
11        // Each corridor connects two rooms bidirectionally
12        for (int[] corridor : corridors) {
13            int room1 = corridor[0];
14            int room2 = corridor[1];
15            graph[room1].add(room2);
16            graph[room2].add(room1);
17        }
18      
19        // Count the number of cycles of length 3 (triangles)
20        int triangleCount = 0;
21      
22        // For each node, check if any pair of its neighbors are also connected
23        for (int currentNode = 1; currentNode <= n; currentNode++) {
24            // Get all neighbors of the current node
25            ArrayList<Integer> neighbors = new ArrayList<>(graph[currentNode]);
26            int neighborCount = neighbors.size();
27          
28            // Check all pairs of neighbors
29            for (int i = 0; i < neighborCount; i++) {
30                for (int j = i + 1; j < neighborCount; j++) {
31                    int neighbor1 = neighbors.get(i);
32                    int neighbor2 = neighbors.get(j);
33                  
34                    // If neighbor1 and neighbor2 are connected, we found a triangle
35                    // Triangle formed by: currentNode, neighbor1, neighbor2
36                    if (graph[neighbor2].contains(neighbor1)) {
37                        triangleCount++;
38                    }
39                }
40            }
41        }
42      
43        // Each triangle is counted 3 times (once for each vertex)
44        // So divide by 3 to get the actual number of unique triangles
45        return triangleCount / 3;
46    }
47}
48
1class Solution {
2public:
3    int numberOfPaths(int n, vector<vector<int>>& corridors) {
4        // Create adjacency list representation of the graph
5        // g[i] stores all nodes connected to node i
6        vector<unordered_set<int>> graph(n + 1);
7      
8        // Build the undirected graph from corridors
9        for (auto& corridor : corridors) {
10            int nodeA = corridor[0];
11            int nodeB = corridor[1];
12            graph[nodeA].insert(nodeB);
13            graph[nodeB].insert(nodeA);
14        }
15      
16        int pathCount = 0;
17      
18        // For each node, check if it can be the middle node of a 3-node cycle
19        for (int middleNode = 1; middleNode <= n; ++middleNode) {
20            // Get all neighbors of the current middle node
21            vector<int> neighbors;
22            neighbors.assign(graph[middleNode].begin(), graph[middleNode].end());
23            int neighborCount = neighbors.size();
24          
25            // Check all pairs of neighbors
26            for (int i = 0; i < neighborCount; ++i) {
27                for (int j = i + 1; j < neighborCount; ++j) {
28                    int firstNeighbor = neighbors[i];
29                    int secondNeighbor = neighbors[j];
30                  
31                    // If these two neighbors are also connected, we found a triangle
32                    // middleNode -> firstNeighbor -> secondNeighbor -> middleNode
33                    if (graph[secondNeighbor].count(firstNeighbor)) {
34                        pathCount++;
35                    }
36                }
37            }
38        }
39      
40        // Each triangle is counted 3 times (once for each node as middle)
41        // So divide by 3 to get the actual number of triangles
42        return pathCount / 3;
43    }
44};
45
1function numberOfPaths(n: number, corridors: number[][]): number {
2    // Create adjacency list representation of the graph
3    // graph[i] stores all nodes connected to node i
4    const graph: Set<number>[] = Array(n + 1).fill(null).map(() => new Set<number>());
5  
6    // Build the undirected graph from corridors
7    for (const corridor of corridors) {
8        const nodeA = corridor[0];
9        const nodeB = corridor[1];
10        graph[nodeA].add(nodeB);
11        graph[nodeB].add(nodeA);
12    }
13  
14    let pathCount = 0;
15  
16    // For each node, check if it can be the middle node of a 3-node cycle
17    for (let middleNode = 1; middleNode <= n; middleNode++) {
18        // Get all neighbors of the current middle node
19        const neighbors: number[] = Array.from(graph[middleNode]);
20        const neighborCount = neighbors.length;
21      
22        // Check all pairs of neighbors
23        for (let i = 0; i < neighborCount; i++) {
24            for (let j = i + 1; j < neighborCount; j++) {
25                const firstNeighbor = neighbors[i];
26                const secondNeighbor = neighbors[j];
27              
28                // If these two neighbors are also connected, we found a triangle
29                // middleNode -> firstNeighbor -> secondNeighbor -> middleNode
30                if (graph[secondNeighbor].has(firstNeighbor)) {
31                    pathCount++;
32                }
33            }
34        }
35    }
36  
37    // Each triangle is counted 3 times (once for each node as middle)
38    // So divide by 3 to get the actual number of triangles
39    return Math.floor(pathCount / 3);
40}
41

Time and Space Complexity

Time Complexity: O(n Γ— dΒ²) where n is the number of rooms and d is the maximum degree (number of connections) of any room.

  • Building the adjacency list from corridors takes O(E) time where E is the number of corridors.
  • The main loop iterates through all n rooms.
  • For each room i, we generate all pairs of its neighbors using combinations(g[i], 2), which produces C(deg(i), 2) = deg(i) Γ— (deg(i) - 1) / 2 pairs where deg(i) is the degree of room i.
  • For each pair (j, k), we check if j in g[k] which takes O(1) average time for a set lookup.
  • In the worst case where one room is connected to many others (degree d), we have O(dΒ²) operations for that room.
  • Overall: O(E + n Γ— dΒ²), which simplifies to O(n Γ— dΒ²) when dΒ² β‰₯ E/n.

Space Complexity: O(E) where E is the number of corridors.

  • The adjacency list g stores each edge twice (once for each direction), requiring O(E) space.
  • The combinations generator and other variables use O(1) additional space.
  • Total space complexity is O(E).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Handling of Empty or Disconnected Graphs

One common pitfall is not properly handling edge cases where rooms have no connections or the graph has isolated vertices. If a room has fewer than 2 neighbors, combinations(graph[room_i], 2) will return an empty iterator, which is handled correctly. However, developers might try to optimize by skipping rooms with insufficient neighbors but introduce bugs in the process.

Incorrect approach:

for room_i in range(1, n + 1):
    if len(graph[room_i]) < 2:  # Might seem like an optimization
        continue
    for room_j, room_k in combinations(graph[room_i], 2):
        # ... rest of the logic

Why it's fine without the check: The combinations function naturally handles sets with fewer than 2 elements by returning an empty iterator, so the explicit check is unnecessary and could introduce maintenance issues.

2. Using Lists Instead of Sets for Adjacency Storage

Using lists instead of sets for storing neighbors can lead to significant performance degradation and potential duplicate counting issues.

Problematic approach:

graph = defaultdict(list)
for room_a, room_b in corridors:
    graph[room_a].append(room_b)
    graph[room_b].append(room_a)

Issues:

  • Checking if room_j in graph[room_k] becomes O(d) instead of O(1) where d is the degree
  • If there are duplicate corridors in the input, they would be added multiple times
  • Overall time complexity degrades from O(n Γ— dΒ²) to O(n Γ— dΒ³)

Solution: Always use defaultdict(set) to ensure O(1) membership checking and automatic handling of duplicate edges.

3. Forgetting to Divide by 3 or Dividing Incorrectly

Each triangle is discovered exactly 3 times (once from each vertex's perspective). A common mistake is either:

  • Forgetting to divide by 3 entirely
  • Using regular division / instead of integer division //
  • Trying to avoid counting triangles multiple times through complex logic instead of the simple divide-by-3 approach

Incorrect approaches:

# Wrong: Using float division
return triangle_count / 3  # Returns float instead of int

# Wrong: Trying to count each triangle only once with complex logic
visited_triangles = set()
for room_i in range(1, n + 1):
    for room_j, room_k in combinations(graph[room_i], 2):
        if room_j in graph[room_k]:
            triangle = tuple(sorted([room_i, room_j, room_k]))
            visited_triangles.add(triangle)
return len(visited_triangles)

Solution: Simply count all triangles and use integer division by 3: return triangle_count // 3

4. Off-by-One Errors with Room Numbering

Since rooms are numbered from 1 to n (not 0 to n-1), iterating incorrectly can miss rooms or cause index errors.

Incorrect approach:

# Wrong: Starting from 0
for room_i in range(n):  # Should be range(1, n + 1)
    # ...

# Wrong: Going up to n-1
for room_i in range(1, n):  # Should be range(1, n + 1)
    # ...

Solution: Always use range(1, n + 1) when iterating through rooms to match the 1-indexed room numbering system.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following problems can be solved with backtracking (select multiple)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More