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 connectingroom1
androom2
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) and1 β 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.
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 ing[k]
(equivalently, ifk
is ing[j]
since the graph is undirected) - If true, rooms
i
,j
, andk
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 EvaluatorExample 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:
- Rooms 1-2-3
- 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 whereE
is the number of corridors. - The main loop iterates through all
n
rooms. - For each room
i
, we generate all pairs of its neighbors usingcombinations(g[i], 2)
, which producesC(deg(i), 2) = deg(i) Γ (deg(i) - 1) / 2
pairs wheredeg(i)
is the degree of roomi
. - For each pair
(j, k)
, we check ifj in g[k]
which takesO(1)
average time for a set lookup. - In the worst case where one room is connected to many others (degree
d
), we haveO(dΒ²)
operations for that room. - Overall:
O(E + n Γ dΒ²)
, which simplifies toO(n Γ dΒ²)
whendΒ² β₯ 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), requiringO(E)
space. - The
combinations
generator and other variables useO(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.
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!