Skip to content

Commit

Permalink
feat(graphs): rotting-oranges
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianLusina committed Oct 3, 2023
1 parent ba2ea38 commit 8e295ac
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 0 deletions.
47 changes: 47 additions & 0 deletions puzzles/graphs/rotting_oranges/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Rotting Oranges

You are given an m x n grid where each cell can have one of three values:

0 representing an empty cell,
1 representing a fresh orange, or
2 representing a rotten orange.
Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten.

Return the minimum number of minutes that must elapse until no cell has a fresh orange. If this is impossible, return
-1.

Example 1

![Rotting Oranges](./rotting_oranges.png)

```plain
Input: grid = [[2,1,1],[1,1,0],[0,1,1]]
Output: 4
```

Example 2:

```plain
Input: grid = [[2,1,1],[0,1,1],[1,0,1]]
Output: -1
```

Explanation: The orange in the bottom left corner (row 2, column 0) is never rotten, because rotting only happens
4-directionally.

Example 3:

```plain
Input: grid = [[0,2]]
Output: 0
```

Explanation: Since there are no fresh oranges at minute 0, the answer is just 0.

## Related Topics

- Graph
- Matrix
- Array
- Breadth-First Search
78 changes: 78 additions & 0 deletions puzzles/graphs/rotting_oranges/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from typing import List, Deque, Tuple
from collections import deque


def oranges_rotting(grid: List[List[int]]) -> int:
"""Returns the minimum time it will take to make all oranges rotten in a grid. If this is not possible, -1 is returned
Since we must track time, a breadth-first search approach makes the most sense, as we can track each iteration of
our bfs loop, and update the time before moving on to the next.
First, we need to iterate our grid looking for rotten oranges to add to our queue. Note, if we also take the time
during this iteration to count the number of fresh oranges, we can save a little time in the end.
Once we have a queue of oranges, we just need to run our BFS and update our time after we exhaust all current cells
in each iteration of it, and finally return the time as long as we have successfully converted all fresh oranges to
rotten ones.
Time Complexity: O(m*n)
where m is the number of rows, and n is the number of columns. We must traverse the graph once to find all our
fresh/rotten oranges, and then potentially again during our BFS.
Space Complexity: O(m*n). In the worst case, all oranges will be rotten, and our queue will be of size m*n.
Args:
grid (List): Grid of oranges.
Returns:
int: minimum number of minutes it will take to make all oranges rotten or -1 if not possible
"""
rows, cols = len(grid), len(grid[0])
# keep track of rotten oranges
queue: Deque[Tuple[int, int]] = deque([])

# number of fresh oranges
fresh = 0

# iterate the grid
for r in range(rows):
for c in range(cols):
if grid[r][c] == 1:
fresh += 1

# add grid position of rotten orange
if grid[r][c] == 2:
queue.append((r, c))

# keep track of time
time = 0
# directions in the form of x,y
directions = ((1, 0), (0, 1), (-1, 0), (0, -1))

# while we have a queue of rotten oranges, and there are still fresh oranges.
while queue and fresh > 0:
for _ in range(len(queue)):
row, col = queue.popleft()

# check the 4 adjacent oranges of this rotten orange
for (dr, dc) in directions:
# position of adjacent orange
r, c = row + dr, col + dc

# check the adjacent orange is in bounds and fresh
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != 1:
continue

# they are fresh, therefore, it becomes rotten
grid[r][c] = 2
fresh -= 1

# add new rotten orange to the queue, we won't reach it on this iteration, as we are only iterating
# through the rotten from the last iteration
queue.append((r, c))

# since we only looped through the rotten oranges inside the queue, and not the adjacent ones, we can increment
# the time now, and on the next iteration we will check those adjacent ones
time += 1

# if we turned all oranges rotten, return the time, else -1
return time if fresh == 0 else -1
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions puzzles/graphs/rotting_oranges/test_rotting_oranges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import unittest
from . import oranges_rotting


class RottingOrangesTestCase(unittest.TestCase):
def test_1(self):
"""should return 4 for grid = [[2,1,1],[1,1,0],[0,1,1]]"""
grid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]
expected = 4
actual = oranges_rotting(grid)
self.assertEqual(expected, actual)

def test_2(self):
"""should return -1 for grid = [[2,1,1],[0,1,1],[1,0,1]]"""
grid = [[2, 1, 1], [0, 1, 1], [1, 0, 1]]
expected = -1
actual = oranges_rotting(grid)
self.assertEqual(expected, actual)

def test_3(self):
"""should return 0 for grid = [[0,2]]"""
grid = [[0, 2]]
expected = 0
actual = oranges_rotting(grid)
self.assertEqual(expected, actual)


if __name__ == '__main__':
unittest.main()

0 comments on commit 8e295ac

Please sign in to comment.