diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java b/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java new file mode 100644 index 000000000000..3ce8696f55ff --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java @@ -0,0 +1,114 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * Multi-source Breadth-First Search (BFS) implementation for the Rotting Oranges problem. + * + *

Algorithm explanation: + * https://en.wikipedia.org/wiki/Breadth-first_search + * + *

Problem reference: + * https://leetcode.com/problems/rotting-oranges/ + * + *

Given a grid where: + *

+ * + *

Returns the minimum number of minutes required for all fresh oranges + * to become rotten. Returns {@code -1} if it is impossible. + * + *

Time Complexity: O(m × n) + *
Space Complexity: O(m × n) + */ +public class RottingOranges { + + private static final int[] DEL_ROW = {-1, 0, 1, 0}; + private static final int[] DEL_COL = {0, 1, 0, -1}; + + private static final class Cell { + private final int row; + private final int col; + private final int minute; + + Cell(int row, int col, int minute) { + this.row = row; + this.col = col; + this.minute = minute; + } + } + + /** + * Executes the Rotting Oranges algorithm. + * + * @param grid the input grid + * @return minimum minutes required to rot all fresh oranges, + * or -1 if impossible + */ + public int run(int[][] grid) { + + if (grid == null || grid.length == 0 || grid[0].length == 0) { + return 0; + } + + int rows = grid.length; + int cols = grid[0].length; + + // Create a copy so original input is not modified + int[][] copy = new int[rows][cols]; + + for (int i = 0; i < rows; i++) { + copy[i] = grid[i].clone(); + } + + Queue queue = new LinkedList<>(); + int freshOranges = 0; + + // Find all rotten oranges and count fresh oranges + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + + if (copy[row][col] == 2) { + queue.offer(new Cell(row, col, 0)); + } else if (copy[row][col] == 1) { + freshOranges++; + } + } + } + + if (freshOranges == 0) { + return 0; + } + + int rottedFresh = 0; + int minutes = 0; + + // Multi-source BFS + while (!queue.isEmpty()) { + + Cell current = queue.poll(); + + minutes = Math.max(minutes, current.minute); + + for (int i = 0; i < 4; i++) { + + int newRow = current.row + DEL_ROW[i]; + int newCol = current.col + DEL_COL[i]; + + if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && copy[newRow][newCol] == 1) { + + copy[newRow][newCol] = 2; + rottedFresh++; + + queue.offer(new Cell(newRow, newCol, current.minute + 1)); + } + } + } + + return rottedFresh == freshOranges ? minutes : -1; + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java new file mode 100644 index 000000000000..003f6fcee812 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java @@ -0,0 +1,134 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class RottingOrangesTest { + + @Test + void testAllOrangesRotInSingleMinute() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 1}, {1, 1, 0}, {0, 1, 1}}; + + assertEquals(4, rottingOranges.run(grid)); + } + + @Test + void testImpossibleToRotAllOranges() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 1}, {0, 1, 1}, {1, 0, 1}}; + + assertEquals(-1, rottingOranges.run(grid)); + } + + @Test + void testNoFreshOranges() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 2}, {2, 2}}; + + assertEquals(0, rottingOranges.run(grid)); + } + + @Test + void testNoRottenOranges() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{1, 1}, {1, 1}}; + + assertEquals(-1, rottingOranges.run(grid)); + } + + @Test + void testEmptyGrid() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {}; + + assertEquals(0, rottingOranges.run(grid)); + } + + @Test + void testSingleRottenOrange() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2}}; + + assertEquals(0, rottingOranges.run(grid)); + } + + @Test + void testSingleFreshOrange() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{1}}; + + assertEquals(-1, rottingOranges.run(grid)); + } + + @Test + void testSingleFreshOrangeNextToRottenOrange() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1}}; + + assertEquals(1, rottingOranges.run(grid)); + } + + @Test + void testMultipleRottenSources() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 0, 2}, {1, 1, 1, 1}, {0, 1, 1, 1}}; + + assertEquals(3, rottingOranges.run(grid)); + } + + @Test + void testFreshOrangeBlockedByEmptyCells() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 0, 1}, {0, 0, 0}, {1, 0, 1}}; + + assertEquals(-1, rottingOranges.run(grid)); + } + + @Test + void testLinearSpread() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 1, 1, 1}}; + + assertEquals(4, rottingOranges.run(grid)); + } + + @Test + void testVerticalSpread() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2}, {1}, {1}, {1}}; + + assertEquals(3, rottingOranges.run(grid)); + } + + @Test + void testGridWithOnlyEmptyCells() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{0, 0}, {0, 0}}; + + assertEquals(0, rottingOranges.run(grid)); + } + + @Test + void testComplexGrid() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 1}, {1, 1, 1}, {1, 1, 1}}; + + assertEquals(4, rottingOranges.run(grid)); + } +}