Skip to content

Commit a47ccbf

Browse files
Merge branch 'master' into feat/sudoku-solver-backtracking
2 parents 767f6c5 + e7f8979 commit a47ccbf

2 files changed

Lines changed: 218 additions & 0 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.thealgorithms.backtracking;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
/**
7+
* Rat in a Maze Problem using Backtracking.
8+
*
9+
* <p>Given an {@code n x n} binary maze where {@code 1} represents an open cell
10+
* and {@code 0} represents a blocked cell, find all paths for a rat starting at
11+
* the top-left cell {@code (0, 0)} to reach the bottom-right cell {@code (n-1, n-1)}.
12+
*
13+
* <p>The rat can move in four directions: Up (U), Down (D), Left (L), Right (R).
14+
* Each cell may be visited at most once per path.
15+
*
16+
* <p>Time Complexity: O(4^(n²)) in the worst case (four choices per cell).
17+
* Space Complexity: O(n²) for the visited matrix and recursion stack.
18+
*
19+
* <p>Example:
20+
* <pre>
21+
* maze = { {1, 0, 0, 0},
22+
* {1, 1, 0, 1},
23+
* {0, 1, 0, 0},
24+
* {0, 1, 1, 1} }
25+
* Output: ["DDRDRR", "DRDDRR"] (two valid paths)
26+
* </pre>
27+
*
28+
* @see <a href="https://en.wikipedia.org/wiki/Maze_solving_algorithm">Maze solving algorithm</a>
29+
* @author the-Sunny-Sharma (<a href="https://github.com/the-Sunny-Sharma">GitHub</a>)
30+
*/
31+
public final class RatInAMaze {
32+
33+
private RatInAMaze() {
34+
}
35+
36+
/**
37+
* Finds all paths from the top-left to the bottom-right of the given maze.
38+
*
39+
* @param maze an {@code n x n} binary matrix where {@code 1} = open, {@code 0} = blocked
40+
* @return a sorted list of all valid path strings using directions D, L, R, U;
41+
* an empty list if no path exists
42+
* @throws IllegalArgumentException if the maze is null, empty, or not square
43+
*/
44+
public static List<String> findPaths(final int[][] maze) {
45+
if (maze == null || maze.length == 0) {
46+
throw new IllegalArgumentException("Maze must not be null or empty.");
47+
}
48+
int n = maze.length;
49+
for (int[] row : maze) {
50+
if (row.length != n) {
51+
throw new IllegalArgumentException("Maze must be a square (n x n) matrix.");
52+
}
53+
}
54+
List<String> results = new ArrayList<>();
55+
if (maze[0][0] == 0 || maze[n - 1][n - 1] == 0) {
56+
return results;
57+
}
58+
boolean[][] visited = new boolean[n][n];
59+
solve(maze, 0, 0, n, "", visited, results);
60+
return results;
61+
}
62+
63+
/**
64+
* Recursive backtracking helper that explores all four directions.
65+
*
66+
* @param maze the binary maze
67+
* @param row current row position
68+
* @param col current column position
69+
* @param n maze dimension
70+
* @param path path string built so far
71+
* @param visited tracks visited cells for the current path
72+
* @param results accumulates complete paths
73+
*/
74+
private static void solve(final int[][] maze, final int row, final int col, final int n, final String path, final boolean[][] visited, final List<String> results) {
75+
// Base case: reached destination
76+
if (row == n - 1 && col == n - 1) {
77+
results.add(path);
78+
return;
79+
}
80+
81+
// Mark current cell as visited
82+
visited[row][col] = true;
83+
84+
// Explore in alphabetical order: Down, Left, Right, Up
85+
// Down
86+
if (isSafe(maze, row + 1, col, n, visited)) {
87+
solve(maze, row + 1, col, n, path + 'D', visited, results);
88+
}
89+
// Left
90+
if (isSafe(maze, row, col - 1, n, visited)) {
91+
solve(maze, row, col - 1, n, path + 'L', visited, results);
92+
}
93+
// Right
94+
if (isSafe(maze, row, col + 1, n, visited)) {
95+
solve(maze, row, col + 1, n, path + 'R', visited, results);
96+
}
97+
// Up
98+
if (isSafe(maze, row - 1, col, n, visited)) {
99+
solve(maze, row - 1, col, n, path + 'U', visited, results);
100+
}
101+
102+
// Backtrack: unmark current cell
103+
visited[row][col] = false;
104+
}
105+
106+
/**
107+
* Checks whether moving to {@code (row, col)} is valid.
108+
*
109+
* @param maze the binary maze
110+
* @param row target row
111+
* @param col target column
112+
* @param n maze dimension
113+
* @param visited tracks visited cells for the current path
114+
* @return {@code true} if the cell is within bounds, open, and not yet visited
115+
*/
116+
private static boolean isSafe(final int[][] maze, final int row, final int col, final int n, final boolean[][] visited) {
117+
return row >= 0 && row < n && col >= 0 && col < n && maze[row][col] == 1 && !visited[row][col];
118+
}
119+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.thealgorithms.backtracking;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.util.List;
8+
import org.junit.jupiter.api.Test;
9+
10+
class RatInAMazeTest {
11+
12+
@Test
13+
void testMultiplePathsExist() {
14+
int[][] maze = {{1, 0, 0, 0}, {1, 1, 0, 1}, {0, 1, 0, 0}, {0, 1, 1, 1}};
15+
16+
List<String> paths = RatInAMaze.findPaths(maze);
17+
assertTrue(paths.size() >= 1);
18+
for (String path : paths) {
19+
assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0));
20+
}
21+
}
22+
23+
@Test
24+
void testSinglePath() {
25+
int[][] maze = {{1, 0, 0}, {1, 1, 0}, {0, 1, 1}};
26+
List<String> paths = RatInAMaze.findPaths(maze);
27+
assertEquals(1, paths.size());
28+
assertEquals("DRDR", paths.get(0));
29+
}
30+
31+
@Test
32+
void testNoPathExists() {
33+
int[][] maze = {{1, 0, 0}, {0, 0, 0}, {0, 0, 1}};
34+
List<String> paths = RatInAMaze.findPaths(maze);
35+
assertTrue(paths.isEmpty());
36+
}
37+
38+
@Test
39+
void testSourceBlocked() {
40+
int[][] maze = {{0, 1}, {1, 1}};
41+
List<String> paths = RatInAMaze.findPaths(maze);
42+
assertTrue(paths.isEmpty());
43+
}
44+
45+
@Test
46+
void testDestinationBlocked() {
47+
int[][] maze = {{1, 1}, {1, 0}};
48+
List<String> paths = RatInAMaze.findPaths(maze);
49+
assertTrue(paths.isEmpty());
50+
}
51+
52+
@Test
53+
void testSingleCellMazeOpen() {
54+
int[][] maze = {{1}};
55+
List<String> paths = RatInAMaze.findPaths(maze);
56+
assertEquals(1, paths.size());
57+
assertEquals("", paths.get(0));
58+
}
59+
60+
@Test
61+
void testSingleCellMazeBlocked() {
62+
int[][] maze = {{0}};
63+
List<String> paths = RatInAMaze.findPaths(maze);
64+
assertTrue(paths.isEmpty());
65+
}
66+
67+
@Test
68+
void testNullMazeThrowsException() {
69+
assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(null));
70+
}
71+
72+
@Test
73+
void testEmptyMazeThrowsException() {
74+
assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(new int[][] {}));
75+
}
76+
77+
@Test
78+
void testNonSquareMazeThrowsException() {
79+
int[][] maze = {{1, 0, 1}, {1, 1, 1}};
80+
assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(maze));
81+
}
82+
83+
@Test
84+
void testAllCellsOpen() {
85+
int[][] maze = {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}};
86+
List<String> paths = RatInAMaze.findPaths(maze);
87+
assertTrue(paths.size() > 1);
88+
}
89+
90+
@Test
91+
void testLargerMazeWithPath() {
92+
int[][] maze = {{1, 1, 1, 1}, {0, 1, 0, 1}, {0, 1, 0, 1}, {0, 1, 1, 1}};
93+
List<String> paths = RatInAMaze.findPaths(maze);
94+
assertTrue(paths.size() >= 1);
95+
for (String path : paths) {
96+
assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0), "Path contains invalid characters: " + path);
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)