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:
+ *
+ * - 0 represents an empty cell
+ * - 1 represents a fresh orange
+ * - 2 represents a rotten orange
+ *
+ *
+ * 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));
+ }
+}
|