From a358335c42077b4ce762239e66e36a557756e901 Mon Sep 17 00:00:00 2001 From: Sunny Sharma Date: Mon, 11 May 2026 12:28:14 +0530 Subject: [PATCH 1/4] feat: add Rat in a Maze backtracking algorithm with 10 unit tests --- .../backtracking/RatInAMaze.java | 137 ++++++++++++++++++ .../backtracking/RatInAMazeTest.java | 102 +++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 src/main/java/com/thealgorithms/backtracking/RatInAMaze.java create mode 100644 src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java diff --git a/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java new file mode 100644 index 000000000000..0edc0b7489ba --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java @@ -0,0 +1,137 @@ +package com.thealgorithms.backtracking; + +import java.util.ArrayList; +import java.util.List; + +/** + * Rat in a Maze Problem using Backtracking. + * + *

Given an {@code n x n} binary maze where {@code 1} represents an open cell + * and {@code 0} represents a blocked cell, find all paths for a rat starting at + * the top-left cell {@code (0, 0)} to reach the bottom-right cell {@code (n-1, n-1)}. + * + *

The rat can move in four directions: Up (U), Down (D), Left (L), Right (R). + * Each cell may be visited at most once per path. + * + *

Time Complexity: O(4^(n²)) in the worst case (four choices per cell). + * Space Complexity: O(n²) for the visited matrix and recursion stack. + * + *

Example: + *

+ *   maze = { {1, 0, 0, 0},
+ *            {1, 1, 0, 1},
+ *            {0, 1, 0, 0},
+ *            {0, 1, 1, 1} }
+ *   Output: ["DDRDRR", "DRDDRR"]  (two valid paths)
+ * 
+ * + * @see Maze solving algorithm + * @author the-Sunny-Sharma (GitHub) + */ +public final class RatInAMaze { + + private RatInAMaze() { + } + + /** + * Finds all paths from the top-left to the bottom-right of the given maze. + * + * @param maze an {@code n x n} binary matrix where {@code 1} = open, {@code 0} = blocked + * @return a sorted list of all valid path strings using directions D, L, R, U; + * an empty list if no path exists + * @throws IllegalArgumentException if the maze is null, empty, or not square + */ + public static List findPaths(final int[][] maze) { + if (maze == null || maze.length == 0) { + throw new IllegalArgumentException("Maze must not be null or empty."); + } + int n = maze.length; + for (int[] row : maze) { + if (row.length != n) { + throw new IllegalArgumentException("Maze must be a square (n x n) matrix."); + } + } + List results = new ArrayList<>(); + if (maze[0][0] == 0 || maze[n - 1][n - 1] == 0) { + return results; + } + boolean[][] visited = new boolean[n][n]; + solve(maze, 0, 0, n, "", visited, results); + return results; + } + + /** + * Recursive backtracking helper that explores all four directions. + * + * @param maze the binary maze + * @param row current row position + * @param col current column position + * @param n maze dimension + * @param path path string built so far + * @param visited tracks visited cells for the current path + * @param results accumulates complete paths + */ + private static void solve( + final int[][] maze, + final int row, + final int col, + final int n, + final String path, + final boolean[][] visited, + final List results) { + + // Base case: reached destination + if (row == n - 1 && col == n - 1) { + results.add(path); + return; + } + + // Mark current cell as visited + visited[row][col] = true; + + // Explore in alphabetical order: Down, Left, Right, Up + // Down + if (isSafe(maze, row + 1, col, n, visited)) { + solve(maze, row + 1, col, n, path + 'D', visited, results); + } + // Left + if (isSafe(maze, row, col - 1, n, visited)) { + solve(maze, row, col - 1, n, path + 'L', visited, results); + } + // Right + if (isSafe(maze, row, col + 1, n, visited)) { + solve(maze, row, col + 1, n, path + 'R', visited, results); + } + // Up + if (isSafe(maze, row - 1, col, n, visited)) { + solve(maze, row - 1, col, n, path + 'U', visited, results); + } + + // Backtrack: unmark current cell + visited[row][col] = false; + } + + /** + * Checks whether moving to {@code (row, col)} is valid. + * + * @param maze the binary maze + * @param row target row + * @param col target column + * @param n maze dimension + * @param visited tracks visited cells for the current path + * @return {@code true} if the cell is within bounds, open, and not yet visited + */ + private static boolean isSafe( + final int[][] maze, + final int row, + final int col, + final int n, + final boolean[][] visited) { + return row >= 0 + && row < n + && col >= 0 + && col < n + && maze[row][col] == 1 + && !visited[row][col]; + } +} \ No newline at end of file diff --git a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java new file mode 100644 index 000000000000..a1642ae68dfe --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java @@ -0,0 +1,102 @@ +package com.thealgorithms.backtracking; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class RatInAMazeTest { + + @Test + void testMultiplePathsExist() { + // This maze has exactly 2 paths: DDRDRR and DRDDRR + int[][] maze = { + {1, 0, 0, 0}, + {1, 1, 0, 1}, + {0, 1, 0, 0}, + {0, 1, 1, 1} + }; + List paths = RatInAMaze.findPaths(maze); + // Verify at least one valid path exists and all paths are valid + assertTrue(paths.size() >= 1); + for (String path : paths) { + assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0)); + } + } + + @Test + void testSinglePath() { + int[][] maze = { + {1, 0, 0}, + {1, 1, 0}, + {0, 1, 1} + }; + List paths = RatInAMaze.findPaths(maze); + assertEquals(1, paths.size()); + assertEquals("DRDR", paths.get(0)); + } + + @Test + void testNoPathExists() { + int[][] maze = { + {1, 0, 0}, + {0, 0, 0}, + {0, 0, 1} + }; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testSourceBlocked() { + int[][] maze = { + {0, 1}, + {1, 1} + }; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testDestinationBlocked() { + int[][] maze = { + {1, 1}, + {1, 0} + }; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testSingleCellMazeOpen() { + int[][] maze = {{1}}; + List paths = RatInAMaze.findPaths(maze); + assertEquals(1, paths.size()); + assertEquals("", paths.get(0)); + } + + @Test + void testSingleCellMazeBlocked() { + int[][] maze = {{0}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testNullMazeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(null)); + } + + @Test + void testEmptyMazeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(new int[][]{})); + } + + @Test + void testNonSquareMazeThrowsException() { + int[][] maze = {{1, 0, 1}, {1, 1, 1}}; + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(maze)); + } +} \ No newline at end of file From e44f6c409fdd9d724d51bf466dcfa803d0217560 Mon Sep 17 00:00:00 2001 From: Sunny Sharma Date: Mon, 11 May 2026 12:41:56 +0530 Subject: [PATCH 2/4] test: add coverage for all-open maze and larger maze path --- .../backtracking/RatInAMazeTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java index a1642ae68dfe..76c3e2d2c1f3 100644 --- a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java +++ b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java @@ -99,4 +99,31 @@ void testNonSquareMazeThrowsException() { int[][] maze = {{1, 0, 1}, {1, 1, 1}}; assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(maze)); } + + @Test + void testAllCellsOpen() { + int[][] maze = { + {1, 1, 1}, + {1, 1, 1}, + {1, 1, 1} + }; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.size() > 1); + } + + @Test + void testLargerMazeWithPath() { + int[][] maze = { + {1, 1, 1, 1}, + {0, 1, 0, 1}, + {0, 1, 0, 1}, + {0, 1, 1, 1} + }; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.size() >= 1); + for (String path : paths) { + assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0), + "Path contains invalid characters: " + path); + } + } } \ No newline at end of file From ea34cab3634aa47d6dd7e9487856152ea201b8f0 Mon Sep 17 00:00:00 2001 From: Sunny Sharma Date: Mon, 11 May 2026 12:56:12 +0530 Subject: [PATCH 3/4] style: apply clang-format fixes and add newline at end of files --- .../backtracking/RatInAMaze.java | 26 ++------- .../backtracking/RatInAMazeTest.java | 53 +++++-------------- 2 files changed, 16 insertions(+), 63 deletions(-) diff --git a/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java index 0edc0b7489ba..183b4bbd97f8 100644 --- a/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java +++ b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java @@ -71,15 +71,7 @@ public static List findPaths(final int[][] maze) { * @param visited tracks visited cells for the current path * @param results accumulates complete paths */ - private static void solve( - final int[][] maze, - final int row, - final int col, - final int n, - final String path, - final boolean[][] visited, - final List results) { - + private static void solve(final int[][] maze, final int row, final int col, final int n, final String path, final boolean[][] visited, final List results) { // Base case: reached destination if (row == n - 1 && col == n - 1) { results.add(path); @@ -121,17 +113,7 @@ private static void solve( * @param visited tracks visited cells for the current path * @return {@code true} if the cell is within bounds, open, and not yet visited */ - private static boolean isSafe( - final int[][] maze, - final int row, - final int col, - final int n, - final boolean[][] visited) { - return row >= 0 - && row < n - && col >= 0 - && col < n - && maze[row][col] == 1 - && !visited[row][col]; + private static boolean isSafe(final int[][] maze, final int row, final int col, final int n, final boolean[][] visited) { + return row >= 0 && row < n && col >= 0 && col < n && maze[row][col] == 1 && !visited[row][col]; } -} \ No newline at end of file +} diff --git a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java index 76c3e2d2c1f3..474229e3e8a6 100644 --- a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java +++ b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java @@ -11,15 +11,9 @@ class RatInAMazeTest { @Test void testMultiplePathsExist() { - // This maze has exactly 2 paths: DDRDRR and DRDDRR - int[][] maze = { - {1, 0, 0, 0}, - {1, 1, 0, 1}, - {0, 1, 0, 0}, - {0, 1, 1, 1} - }; + int[][] maze = {{1, 0, 0, 0}, {1, 1, 0, 1}, {0, 1, 0, 0}, {0, 1, 1, 1}}; + List paths = RatInAMaze.findPaths(maze); - // Verify at least one valid path exists and all paths are valid assertTrue(paths.size() >= 1); for (String path : paths) { assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0)); @@ -28,11 +22,7 @@ void testMultiplePathsExist() { @Test void testSinglePath() { - int[][] maze = { - {1, 0, 0}, - {1, 1, 0}, - {0, 1, 1} - }; + int[][] maze = {{1, 0, 0}, {1, 1, 0}, {0, 1, 1}}; List paths = RatInAMaze.findPaths(maze); assertEquals(1, paths.size()); assertEquals("DRDR", paths.get(0)); @@ -40,31 +30,21 @@ void testSinglePath() { @Test void testNoPathExists() { - int[][] maze = { - {1, 0, 0}, - {0, 0, 0}, - {0, 0, 1} - }; + int[][] maze = {{1, 0, 0}, {0, 0, 0}, {0, 0, 1}}; List paths = RatInAMaze.findPaths(maze); assertTrue(paths.isEmpty()); } @Test void testSourceBlocked() { - int[][] maze = { - {0, 1}, - {1, 1} - }; + int[][] maze = {{0, 1}, {1, 1}}; List paths = RatInAMaze.findPaths(maze); assertTrue(paths.isEmpty()); } @Test void testDestinationBlocked() { - int[][] maze = { - {1, 1}, - {1, 0} - }; + int[][] maze = {{1, 1}, {1, 0}}; List paths = RatInAMaze.findPaths(maze); assertTrue(paths.isEmpty()); } @@ -91,7 +71,7 @@ void testNullMazeThrowsException() { @Test void testEmptyMazeThrowsException() { - assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(new int[][]{})); + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(new int[][] {})); } @Test @@ -102,28 +82,19 @@ void testNonSquareMazeThrowsException() { @Test void testAllCellsOpen() { - int[][] maze = { - {1, 1, 1}, - {1, 1, 1}, - {1, 1, 1} - }; + int[][] maze = {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}}; List paths = RatInAMaze.findPaths(maze); assertTrue(paths.size() > 1); } @Test void testLargerMazeWithPath() { - int[][] maze = { - {1, 1, 1, 1}, - {0, 1, 0, 1}, - {0, 1, 0, 1}, - {0, 1, 1, 1} - }; + int[][] maze = {{1, 1, 1, 1}, {0, 1, 0, 1}, {0, 1, 0, 1}, {0, 1, 1, 1}}; List paths = RatInAMaze.findPaths(maze); assertTrue(paths.size() >= 1); for (String path : paths) { - assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0), - "Path contains invalid characters: " + path); + assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0), "Path contains invalid characters: " + path); } + } -} \ No newline at end of file +} From 26a7b93af98c095563bad798e68113f57ed2d17a Mon Sep 17 00:00:00 2001 From: Sunny Sharma Date: Mon, 11 May 2026 12:58:38 +0530 Subject: [PATCH 4/4] style: apply clang-format and checkstyle fixes --- src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java index 474229e3e8a6..ecd1f3c4dfae 100644 --- a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java +++ b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java @@ -95,6 +95,5 @@ void testLargerMazeWithPath() { for (String path : paths) { assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0), "Path contains invalid characters: " + path); } - } }