2257. Count Unguarded Cells in the Grid
Problem Description
You have a grid with m
rows and n
columns (0-indexed). In this grid, there are guards and walls placed at specific positions.
guards[i] = [row_i, col_i]
gives the position of the i-th guardwalls[j] = [row_j, col_j]
gives the position of the j-th wall
A guard can see in all four cardinal directions (north, east, south, west) from their position. Their vision extends indefinitely in each direction until blocked by either a wall or another guard. Any cell that can be seen by at least one guard is considered "guarded."
The task is to count how many cells are:
- Unoccupied (not containing a guard or wall)
- Not guarded (cannot be seen by any guard)
For example, if a guard is at position (1, 1)
in a 3×3 grid with no walls:
- The guard can see cells
(0, 1)
,(2, 1)
horizontally - The guard can see cells
(1, 0)
,(1, 2)
vertically - These cells would be considered "guarded"
Return the total number of unoccupied cells that are not guarded.
Intuition
The key insight is that we need to track the state of each cell in the grid. Each cell can be in one of three states:
- Contains a guard or wall (occupied)
- Can be seen by at least one guard (guarded)
- Cannot be seen by any guard (unguarded)
We can use a 2D array to represent the grid and mark different states with different values. Let's use:
0
for unguarded cells1
for guarded cells2
for cells with guards or walls
The simulation approach becomes natural when we think about how a guard's vision works. From each guard's position, we need to "shoot rays" in four cardinal directions. Each ray continues until it hits an obstacle (another guard or wall) or reaches the grid boundary.
Why simulate from each guard individually? Because each guard contributes independently to the guarded area. A cell becomes guarded if ANY guard can see it, so we need to check all guards.
The direction vectors (-1, 0, 1, 0, -1)
cleverly encode the four directions when taken in pairs: up (-1, 0)
, right (0, 1)
, down (1, 0)
, and left (0, -1)
. This allows us to iterate through all four directions with a simple loop.
During the ray-casting process, we only mark cells with value less than 2
(meaning they're not occupied by guards/walls) as guarded. This ensures we stop at obstacles while marking all visible empty cells.
Finally, counting cells with value 0
gives us the unguarded, unoccupied cells - exactly what the problem asks for.
Solution Approach
We implement the simulation approach using a 2D array to track cell states.
Step 1: Initialize the Grid
Create a 2D array g
of size m × n
initialized with all zeros:
g = [[0] * n for _ in range(m)]
0
represents unguarded cells1
will represent guarded cells2
will represent occupied cells (guards/walls)
Step 2: Mark Occupied Positions
First, mark all guard and wall positions with value 2
:
for i, j in guards: g[i][j] = 2 for i, j in walls: g[i][j] = 2
This prevents us from marking these cells as guarded and stops our vision rays at these positions.
Step 3: Simulate Guard Vision
For each guard, cast rays in four cardinal directions:
dirs = (-1, 0, 1, 0, -1) for i, j in guards: for a, b in pairwise(dirs): x, y = i, j while 0 <= x + a < m and 0 <= y + b < n and g[x + a][y + b] < 2: x, y = x + a, y + b g[x][y] = 1
The pairwise(dirs)
creates direction vectors: (-1, 0)
for up, (0, 1)
for right, (1, 0)
for down, (0, -1)
for left.
For each direction:
- Start from the guard's position
(i, j)
- Move step by step in that direction
(x + a, y + b)
- Continue while:
- We stay within grid bounds:
0 <= x + a < m and 0 <= y + b < n
- The next cell is not occupied:
g[x + a][y + b] < 2
- We stay within grid bounds:
- Mark each visited cell as guarded:
g[x][y] = 1
Step 4: Count Unguarded Cells
Count all cells that remain with value 0
:
return sum(v == 0 for row in g for v in row)
This double iteration counts every cell in the grid that is neither occupied nor guarded.
Time Complexity: O(m × n × (m + n))
in the worst case, as each guard might need to check up to m + n
cells.
Space Complexity: O(m × n)
for the grid array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with a 4×3 grid, 2 guards at positions [(1,1), (2,0)]
, and 1 wall at position [(0,2)]
.
Step 1: Initialize the Grid We create a 4×3 grid filled with zeros:
0 0 0 0 0 0 0 0 0 0 0 0
Step 2: Mark Occupied Positions Place guards (G) and wall (W) by marking them with value 2:
0 0 W(2) 0 G(2) 0 G(2) 0 0 0 0 0
Step 3: Simulate Guard Vision
For guard at (1,1)
:
- Up direction (-1,0): Move from (1,1) to (0,1). Mark (0,1) as guarded (1). Can't continue - reached grid boundary.
- Right direction (0,1): Move from (1,1) to (1,2). Mark (1,2) as guarded (1). Can't continue - reached grid boundary.
- Down direction (1,0): Move from (1,1) to (2,1). Mark (2,1) as guarded (1). Continue to (3,1), mark as guarded (1). Can't continue - reached grid boundary.
- Left direction (0,-1): Move from (1,1) to (1,0). Mark (1,0) as guarded (1). Can't continue - reached grid boundary.
Grid after first guard:
0 1 W(2) 1 G(2) 1 G(2) 1 0 0 1 0
For guard at (2,0)
:
- Up direction (-1,0): Move from (2,0) to (1,0). Cell already guarded (1), mark stays as 1. Continue to (0,0), mark as guarded (1). Can't continue - reached grid boundary.
- Right direction (0,1): Move from (2,0) to (2,1). Cell already guarded (1), mark stays as 1. Continue to (2,2), mark as guarded (1). Can't continue - reached grid boundary.
- Down direction (1,0): Move from (2,0) to (3,0). Mark (3,0) as guarded (1). Can't continue - reached grid boundary.
- Left direction (0,-1): Can't move - would go out of bounds immediately.
Final grid state:
1 1 W(2) 1 G(2) 1 G(2) 1 1 1 1 0
Step 4: Count Unguarded Cells Count cells with value 0: Only cell (3,2) has value 0.
Answer: 1 unguarded, unoccupied cell
Solution Implementation
1class Solution:
2 def countUnguarded(
3 self, m: int, n: int, guards: List[List[int]], walls: List[List[int]]
4 ) -> int:
5 # Initialize grid: 0 = unguarded, 1 = guarded, 2 = guard/wall
6 grid = [[0] * n for _ in range(m)]
7
8 # Mark guards on the grid
9 for row, col in guards:
10 grid[row][col] = 2
11
12 # Mark walls on the grid
13 for row, col in walls:
14 grid[row][col] = 2
15
16 # Direction vectors: up, right, down, left
17 directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
18
19 # For each guard, mark all cells they can see in four directions
20 for guard_row, guard_col in guards:
21 for delta_row, delta_col in directions:
22 current_row, current_col = guard_row, guard_col
23
24 # Continue in current direction until hitting boundary or obstacle
25 while (0 <= current_row + delta_row < m and
26 0 <= current_col + delta_col < n and
27 grid[current_row + delta_row][current_col + delta_col] < 2):
28 current_row += delta_row
29 current_col += delta_col
30 grid[current_row][current_col] = 1 # Mark as guarded
31
32 # Count unguarded cells (cells with value 0)
33 unguarded_count = sum(cell == 0 for row in grid for cell in row)
34
35 return unguarded_count
36
1class Solution {
2 public int countUnguarded(int m, int n, int[][] guards, int[][] walls) {
3 // Initialize grid: 0 = unguarded, 1 = guarded, 2 = guard/wall
4 int[][] grid = new int[m][n];
5
6 // Mark all guard positions as 2
7 for (int[] guard : guards) {
8 grid[guard[0]][guard[1]] = 2;
9 }
10
11 // Mark all wall positions as 2
12 for (int[] wall : walls) {
13 grid[wall[0]][wall[1]] = 2;
14 }
15
16 // Direction vectors: up, right, down, left
17 int[] directions = {-1, 0, 1, 0, -1};
18
19 // For each guard, mark all cells it can see in 4 directions
20 for (int[] guard : guards) {
21 // Check all 4 directions
22 for (int dirIndex = 0; dirIndex < 4; dirIndex++) {
23 int currentRow = guard[0];
24 int currentCol = guard[1];
25 int rowDelta = directions[dirIndex];
26 int colDelta = directions[dirIndex + 1];
27
28 // Extend vision in current direction until hitting boundary or obstacle
29 while (currentRow + rowDelta >= 0 &&
30 currentRow + rowDelta < m &&
31 currentCol + colDelta >= 0 &&
32 currentCol + colDelta < n &&
33 grid[currentRow + rowDelta][currentCol + colDelta] < 2) {
34
35 currentRow += rowDelta;
36 currentCol += colDelta;
37 grid[currentRow][currentCol] = 1; // Mark as guarded
38 }
39 }
40 }
41
42 // Count unguarded cells (cells with value 0)
43 int unguardedCount = 0;
44 for (int[] row : grid) {
45 for (int cell : row) {
46 if (cell == 0) {
47 unguardedCount++;
48 }
49 }
50 }
51
52 return unguardedCount;
53 }
54}
55
1class Solution {
2public:
3 int countUnguarded(int m, int n, vector<vector<int>>& guards, vector<vector<int>>& walls) {
4 // Create a grid to track cell states:
5 // 0 = unguarded, 1 = guarded (visible by guard), 2 = occupied (guard or wall)
6 vector<vector<int>> grid(m, vector<int>(n, 0));
7
8 // Mark all guard positions as occupied
9 for (const auto& guard : guards) {
10 grid[guard[0]][guard[1]] = 2;
11 }
12
13 // Mark all wall positions as occupied
14 for (const auto& wall : walls) {
15 grid[wall[0]][wall[1]] = 2;
16 }
17
18 // Direction vectors for moving up, right, down, left
19 // Using pairs: (dx[i], dy[i]) represents one direction
20 int dx[] = {-1, 0, 1, 0};
21 int dy[] = {0, 1, 0, -1};
22
23 // For each guard, mark all cells they can see in four directions
24 for (const auto& guard : guards) {
25 // Check all four directions
26 for (int dir = 0; dir < 4; ++dir) {
27 int currentRow = guard[0];
28 int currentCol = guard[1];
29 int rowDelta = dx[dir];
30 int colDelta = dy[dir];
31
32 // Continue in current direction until hitting boundary or obstacle
33 while (currentRow + rowDelta >= 0 &&
34 currentRow + rowDelta < m &&
35 currentCol + colDelta >= 0 &&
36 currentCol + colDelta < n &&
37 grid[currentRow + rowDelta][currentCol + colDelta] < 2) {
38
39 // Move to next cell in current direction
40 currentRow += rowDelta;
41 currentCol += colDelta;
42
43 // Mark this cell as guarded
44 grid[currentRow][currentCol] = 1;
45 }
46 }
47 }
48
49 // Count all unguarded cells (cells with value 0)
50 int unguardedCount = 0;
51 for (int row = 0; row < m; ++row) {
52 for (int col = 0; col < n; ++col) {
53 if (grid[row][col] == 0) {
54 unguardedCount++;
55 }
56 }
57 }
58
59 return unguardedCount;
60 }
61};
62
1/**
2 * Counts the number of unguarded cells in a grid with guards and walls
3 * @param m - Number of rows in the grid
4 * @param n - Number of columns in the grid
5 * @param guards - Array of guard positions [row, col]
6 * @param walls - Array of wall positions [row, col]
7 * @returns Number of unguarded cells
8 */
9function countUnguarded(m: number, n: number, guards: number[][], walls: number[][]): number {
10 // Initialize grid: 0 = unguarded, 1 = guarded, 2 = guard/wall
11 const grid: number[][] = Array.from({ length: m }, () =>
12 Array.from({ length: n }, () => 0)
13 );
14
15 // Mark guard positions in the grid
16 for (const [row, col] of guards) {
17 grid[row][col] = 2;
18 }
19
20 // Mark wall positions in the grid
21 for (const [row, col] of walls) {
22 grid[row][col] = 2;
23 }
24
25 // Direction vectors: up, right, down, left
26 const directions: number[] = [-1, 0, 1, 0, -1];
27
28 // Process each guard's line of sight in all four directions
29 for (const [guardRow, guardCol] of guards) {
30 for (let dirIndex = 0; dirIndex < 4; dirIndex++) {
31 let currentRow: number = guardRow;
32 let currentCol: number = guardCol;
33 const rowDelta: number = directions[dirIndex];
34 const colDelta: number = directions[dirIndex + 1];
35
36 // Extend guard's vision in current direction until hitting boundary or obstacle
37 while (currentRow + rowDelta >= 0 &&
38 currentRow + rowDelta < m &&
39 currentCol + colDelta >= 0 &&
40 currentCol + colDelta < n &&
41 grid[currentRow + rowDelta][currentCol + colDelta] < 2) {
42 currentRow += rowDelta;
43 currentCol += colDelta;
44 grid[currentRow][currentCol] = 1; // Mark as guarded
45 }
46 }
47 }
48
49 // Count unguarded cells (cells with value 0)
50 let unguardedCount: number = 0;
51 for (const row of grid) {
52 for (const cellValue of row) {
53 unguardedCount += cellValue === 0 ? 1 : 0;
54 }
55 }
56
57 return unguardedCount;
58}
59
Time and Space Complexity
Time Complexity: O(m × n × (g + w))
The algorithm consists of:
- Grid initialization:
O(m × n)
- Marking guards:
O(g)
whereg
is the number of guards - Marking walls:
O(w)
wherew
is the number of walls - Guard vision calculation: For each guard, we check in 4 directions. In the worst case, each direction traverses the entire row or column, giving
O(g × (m + n))
. However, since multiple guards can traverse the same cells, the total cells visited across all guards and directions is bounded byO(m × n)
for each direction, resulting inO(m × n)
overall. - Counting unguarded cells:
O(m × n)
The dominant operation is the guard vision calculation which visits each cell at most 4 times (once from each direction), giving a total time complexity of O(m × n)
.
Space Complexity: O(m × n)
The algorithm uses a 2D grid g
of size m × n
to track the state of each cell (0 for unguarded, 1 for guarded, 2 for guard/wall). No additional significant space is used, resulting in space complexity of O(m × n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Marking Guard/Wall Positions After Processing Vision
The Problem: A common mistake is to mark the guard positions as occupied (value 2) AFTER simulating their vision. This causes the guard's own position to be counted as "guarded" (value 1) when another guard looks in that direction.
Incorrect Implementation:
# Process guard vision first for guard_row, guard_col in guards: for delta_row, delta_col in directions: # ... cast vision rays ... # Then mark guards - TOO LATE! for row, col in guards: grid[row][col] = 2
Why This Fails: When Guard A at position (1, 1) casts vision and then Guard B at position (1, 3) casts vision leftward, Guard B would mark Guard A's position as "guarded" instead of stopping at it. This leads to incorrect counting.
The Solution: Always mark all occupied positions (both guards and walls) BEFORE processing any guard's vision:
# Mark all occupied positions first for row, col in guards: grid[row][col] = 2 for row, col in walls: grid[row][col] = 2 # Then process vision for guard_row, guard_col in guards: # ... cast vision rays ...
Pitfall 2: Incorrect Boundary Check in While Loop
The Problem: Using the wrong boundary check can cause index out of bounds errors or miss valid cells.
Incorrect Implementation:
while (current_row + delta_row < m and current_col + delta_col < n and grid[current_row + delta_row][current_col + delta_col] < 2): # Missing check for negative indices!
Why This Fails:
When moving upward (delta_row = -1) or leftward (delta_col = -1), current_row + delta_row
or current_col + delta_col
could become negative, causing Python to access the array from the end (negative indexing) instead of stopping.
The Solution: Include both lower and upper bound checks:
while (0 <= current_row + delta_row < m and 0 <= current_col + delta_col < n and grid[current_row + delta_row][current_col + delta_col] < 2):
Pitfall 3: Overwriting Guard Vision Information
The Problem: If you use the same value to mark both guards/walls and guarded cells, you lose the ability to distinguish between them.
Incorrect Implementation:
# Using value 1 for both guards/walls and guarded cells for row, col in guards: grid[row][col] = 1 # Same value as guarded!
Why This Fails: When a guard tries to cast vision, it won't know whether a cell with value 1 is another guard (should stop) or just a guarded cell (should continue).
The Solution: Use distinct values for different states:
- 0 = unguarded and unoccupied
- 1 = guarded (but unoccupied)
- 2 = occupied (guard or wall)
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
Coding Interview 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
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 assets algo monster recursion jpg You first call Ben and ask
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!