2257. Count Unguarded Cells in the Grid
Problem Description
This LeetCode problem requires us to calculate the number of unoccupied cells in a 0-indexed m x n grid that are not guarded by any guards and not blocked by walls. We are given two lists, guards
and walls
, where each element is a pair of coordinates representing the location of a guard or a wall, respectively. A guard can see and thus guard all cells in the four cardinal directions (north, east, south, or west) unless there is a wall or another guard obstructing the view. Cells are considered guarded if at least one guard can see them. Our goal is to count how many cells are left unguarded, considering that walls and guards themselves occupy some of the cells.
Intuition
The solution to this problem is based on simulating the guards' line of sight and marking cells they can "see" as guarded. By creating a grid, we can simulate the guards' lines of sight in each of the four cardinal directions. We can iterate through each direction from the guard's position until we either reach the grid's boundary, encounter another guard, or a wall, which blocks the guard's line of sight. The key things to keep in mind are:
- Guards and walls occupy some cells, so these should be marked differently to indicate they are not empty and cannot be guarded by other guards.
- We need to check the visibility in all directions from each guard until we are obstructed or out of bounds.
- After marking all the guarded cells, anything left as unmarked is unguarded, and we just need to count those cells.
By iterating over the guards' positions and simulating their view lines while bypassing walls and other guards, we can then easily count the number of unobserved cells to get our answer.
Solution Approach
The implementation of the solution follows a step-by-step approach which relies on simulation and proper labeling of the cells within the grid.
Here's a walkthrough of the algorithm:
-
Initialization: We start by creating a grid
g
sizedm x n
, which will serve as a representation of our problem space. The value of each cell ing
initially is0
, which stands for unguarded and unoccupied by either a guard or a wall. -
Marking Guards and Walls: We loop through the
guards
andwalls
arrays, marking the corresponding cells in our gridg
as2
. This2
is a special label indicating cells that are occupied by either a guard or a wall and cannot be further guarded. -
Guarding Cells: Next, we iterate over all the guard positions. Since guards can see in the four cardinal directions, we simulate this visibility. To do this, we use the
dirs
tuple which defines the directional steps for north, east, south, and west. For each direction:- We start from the guard's position and move cell by cell in the current direction.
- The movement continues until we reach a cell that is either out of bounds, marked as
2
(a wall or another guard), or has already been guarded. - As we move through cells, we change their status to
1
to signify that they are guarded.
This simulation uses the
pairwise
iterator from thedirs
tuple to get the direction vectors, which define our movement across the grid from the guard's current position. -
Counting Unguarded Cells: Finally, we go through the entire grid
g
and count all cells still marked as0
, which denotes they are unoccupied and unguarded.
Therefore, the algorithm employs a simulation method and utilizes a matrix to keep track of the state of each cell accurately. This is an efficient way to solve the problem as it does not involve any complex data structures or algorithms, just straightforward iterative logic and condition checking.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a simple example to illustrate the solution approach. Suppose we have a 3x3 grid represented by an m x n
matrix, where m = n = 3
. Imagine our grid looks something like this:
[ ][ ][ ] [ ][ ][ ] [ ][ ][ ]
Now, let's say we have one guard and one wall, with the guards
list as [(1, 1)]
and the walls
list as [(0, 2)]
. The guard is located in the cell at row 1 column 1 (use 0-indexing), and the wall is located at row 0 column 2. The grid now looks like this, with 'G' representing the guard and 'W' the wall:
[ ][ ][W] [ ][G][ ] [ ][ ][ ]
Following the solution approach:
- Initialization: We create an initial grid
g
of size 3x3 with all cells initialized to0
to represent unguarded cells.
[0][0][0] [0][0][0] [0][0][0]
- Marking Guards and Walls: We mark the guard's and wall's positions on the grid with
2
. The grid now looks like this:
[0][0][2] [0][2][0] [0][0][0]
-
Guarding Cells: Since the guard can look in all four directions, we will simulate this for our single guard at
(1, 1)
:- North Direction (upwards): We check the cell
(0, 1)
. Since it's a0
, we mark it as1
. The cell(0, 2)
won't be checked because there's a wall at(0, 2)
. - East Direction (to the right): The guard checks the cell
(1, 2)
. It's a0
, so we mark it as1
. - South Direction (downwards): The guard checks the cell
(2, 1)
. It's a0
, so we mark it as1
. - West Direction (to the left): The guard checks the cell
(1, 0)
. It's a0
, so we mark it as1
.
- North Direction (upwards): We check the cell
After the guard has scanned all four directions, the grid now looks like:
[0][1][2] [1][2][1] [0][1][0]
- Counting Unguarded Cells: We count all
0
s in the grid to find the unguarded cells. There are three0
s, which means there are three unguarded cells.
This simple example demonstrates how the solution approach accurately simulates guards’ visibility to find unguarded cells in the grid.
Solution Implementation
1from typing import List
2
3class Solution:
4 def countUnguarded(self, m: int, n: int, guards: List[List[int]], walls: List[List[int]]) -> int:
5 # Initialize the grid with 0 to represent unguarded cells
6 grid = [[0] * n for _ in range(m)]
7
8 # Mark the positions of guards and walls with 2 on the grid
9 for guard_row, guard_col in guards:
10 grid[guard_row][guard_col] = 2
11 for wall_row, wall_col in walls:
12 grid[wall_row][wall_col] = 2
13
14 # Define the directions in which guards look (up, right, down, left)
15 directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
16
17 # Scan the grid for guards and set the cells they see to 1
18 for guard_row, guard_col in guards:
19 for delta_row, delta_col in directions:
20 row, col = guard_row, guard_col
21 # Continue marking cells in current direction until a wall or guard is reached
22 while 0 <= row + delta_row < m and 0 <= col + delta_col < n and grid[row + delta_row][col + delta_col] < 2:
23 row, col = row + delta_row, col + delta_col
24 grid[row][col] = 1
25
26 # Count the number of cells that are not guarded (value 0 in the grid)
27 return sum(cell == 0 for row in grid for cell in row)
28
1class Solution {
2 public int countUnguarded(int m, int n, int[][] guards, int[][] walls) {
3 // Initialize the grid representation where
4 // 0 represents an unguarded cell,
5 // 1 represents a cell guarded by a guard's line of sight,
6 // 2 represents an occupied cell by either a guard or a wall.
7 int[][] grid = new int[m][n];
8
9 // Mark all the guard positions on the grid.
10 for (int[] guard : guards) {
11 grid[guard[0]][guard[1]] = 2;
12 }
13
14 // Mark all the wall positions on the grid.
15 for (int[] wall : walls) {
16 grid[wall[0]][wall[1]] = 2;
17 }
18
19 // This array represents the directional increments: up, right, down, and left.
20 int[] directions = {-1, 0, 1, 0, -1};
21
22 // Iterate over each guard and mark their line of sight until they hit a wall or the grid's edge.
23 for (int[] guard : guards) {
24 // Four directions - up, right, down, left
25 for (int k = 0; k < 4; ++k) {
26 // Starting position for the current guard.
27 int x = guard[0], y = guard[1];
28 // Directional increments for the current direction.
29 int deltaX = directions[k], deltaY = directions[k + 1];
30 // Keep marking the grid in the current direction till
31 // you hit a wall, guard or boundary of the grid.
32 while (x + deltaX >= 0 && x + deltaX < m && y + deltaY >= 0 && y + deltaY < n && grid[x + deltaX][y + deltaY] < 2) {
33 x += deltaX;
34 y += deltaY;
35 grid[x][y] = 1; // Marking the cell as guarded.
36 }
37 }
38 }
39
40 // Count all the unguarded spaces, where the grid value is 0.
41 int unguardedCount = 0;
42 for (int[] row : grid) {
43 for (int cellValue : row) {
44 if (cellValue == 0) {
45 unguardedCount++;
46 }
47 }
48 }
49
50 // Return the total number of unguarded cells.
51 return unguardedCount;
52 }
53}
54
1#include <vector>
2#include <cstring> // for memset
3using namespace std;
4
5class Solution {
6public:
7 // Function to count unguarded cells in a matrix representing a room
8 int countUnguarded(int rows, int cols, vector<vector<int>>& guards, vector<vector<int>>& walls) {
9 int grid[rows][cols];
10 memset(grid, 0, sizeof(grid)); // Initialize the entire grid to 0
11
12 // Set guard positions to 2 in the grid
13 for (auto& guard : guards) {
14 grid[guard[0]][guard[1]] = 2;
15 }
16
17 // Set wall positions to 2 in the grid
18 for (auto& wall : walls) {
19 grid[wall[0]][wall[1]] = 2;
20 }
21
22 // Direction vectors to move up, right, down, or left
23 int directions[5] = {-1, 0, 1, 0, -1};
24
25 // Visit each guard position and mark guarded areas
26 for (auto& guard : guards) {
27 // Observing in all 4 directions from the guard position
28 for (int k = 0; k < 4; ++k) {
29 int x = guard[0], y = guard[1]; // Starting guard's position
30 int dx = directions[k], dy = directions[k + 1]; // Direction changes
31
32 // Move in the current direction until hitting a wall, guard, or boundary
33 while (x + dx >= 0 && x + dx < rows && y + dy >= 0 && y + dy < cols && grid[x + dx][y + dy] < 2) {
34 x += dx;
35 y += dy;
36 grid[x][y] = 1; // Mark the cell as guarded
37 }
38 }
39 }
40
41 // Count unguarded cells (where grid value is still 0)
42 int unguardedCount = 0;
43 for (int i = 0; i < rows; ++i) {
44 for (int j = 0; j < cols; ++j) {
45 if (grid[i][j] == 0) {
46 unguardedCount++;
47 }
48 }
49 }
50 return unguardedCount;
51 }
52};
53
1function countUnguarded(maxRows: number, maxCols: number, guards: number[][], walls: number[][]): number {
2 // Create a grid to represent the museum with default values set to 0
3 const grid: number[][] = Array.from({ length: maxRows }, () => Array.from({ length: maxCols }, () => 0));
4
5 // Mark the positions of guards with 2 on the grid
6 for (const [row, col] of guards) {
7 grid[row][col] = 2;
8 }
9
10 // Mark the positions of walls with 2 on the grid
11 for (const [row, col] of walls) {
12 grid[row][col] = 2;
13 }
14
15 // Directions array to facilitate exploration in the 4 cardinal directions
16 const directions: number[] = [-1, 0, 1, 0, -1];
17
18 // For each guard, mark the positions they can guard based on the grid constraints
19 for (const [guardRow, guardCol] of guards) {
20 // Check all directions: up, right, down, and left
21 for (let k = 0; k < 4; ++k) {
22 // Initialize guard's position to start casting
23 let [x, y] = [guardRow, guardCol];
24 let [deltaX, deltaY] = [directions[k], directions[k + 1]];
25
26 // Move in the current direction as long as it's within bounds and not blocked by walls or other guards
27 while (x + deltaX >= 0 && x + deltaX < maxRows && y + deltaY >= 0 && y + deltaY < maxCols && grid[x + deltaX][y + deltaY] < 2) {
28 x += deltaX;
29 y += deltaY;
30 // Mark the guarded position with a 1
31 grid[x][y] = 1;
32 }
33 }
34 }
35
36 // Count the number of unguarded positions in the grid
37 let unguardedCount = 0;
38 for (const row of grid) {
39 for (const cell of row) {
40 if (cell === 0) {
41 unguardedCount++;
42 }
43 }
44 }
45
46 // Return the count of unguarded positions
47 return unguardedCount;
48}
49
Time and Space Complexity
Time Complexity
The time complexity of the code is O(m * n + g * (m + n))
, where m
and n
correspond to the number of rows and columns of the grid, and g
is the number of guards. The reasoning behind this is as follows:
- The initial setup of the grid
g
with sizem * n
takesO(m * n)
time. - The loops for placing guards and walls each run at most
O(g + w)
, wherew
is the number of walls, however, these are negligible compared to other terms wheng
andw
are much smaller thanm * n
. - The nested loops iterate through each guard, and for each guard, scan across the grid in four directions up to
m
orn
times (whichever direction taken), hence4*(m + n)
for each guard, aggregating tog * (m + n)
for all guards.
Space Complexity
The space complexity of the algorithm is O(m * n)
. This is because only a single m * n
grid is used as extra space to store the state of each cell (whether it is free, guarded, or a wall).
Learn more about how to find time and space complexity quickly using problem constraints.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!