LeetCode 1034 - Coloring A Border
The problem gives us a two dimensional grid where each cell contains an integer representing a color. We are also given a starting position, (row, col), and a new color value. The cell at grid[row][col] belongs to some connected component.
Difficulty: 🟡 Medium
Topics: Array, Depth-First Search, Breadth-First Search, Matrix
Solution
Problem Understanding
The problem gives us a two dimensional grid where each cell contains an integer representing a color. We are also given a starting position, (row, col), and a new color value.
The cell at grid[row][col] belongs to some connected component. A connected component is formed by cells that:
- Have the same original color
- Are connected through up, down, left, or right moves
The task is not to recolor the entire component. Instead, we only recolor the border cells of that component.
A cell is considered part of the border if either:
- It lies on the outer edge of the grid
- At least one of its four neighbors is not part of the same connected component
Every non-border cell inside the component must keep its original color.
The output should be the modified grid after recoloring only the border cells.
The constraints are relatively small:
m, n <= 50- Total cells are at most
2500
This means a graph traversal solution such as Depth-First Search (DFS) or Breadth-First Search (BFS) is completely feasible. Even an O(m * n) traversal is efficient enough.
There are several important edge cases to consider.
If the connected component contains only one cell, then that cell is automatically a border because it touches the boundary of the component. It must be recolored.
If the entire component fills the whole grid, then only the outer ring is recolored while interior cells remain unchanged.
If the new color is the same as the original color, we still need to correctly identify border cells, although the final grid may visually remain unchanged.
Another subtle case occurs when we recolor cells while traversing. If we immediately overwrite cell values, we may accidentally break future connectivity checks. Because of this, we must either:
- Keep a separate visited structure
- Delay recoloring until after traversal
- Use temporary markers carefully
Approaches
Brute Force Approach
A brute force strategy would try to determine for every cell in the grid whether it belongs to the connected component and whether it is a border.
One possible implementation is:
- Start from
(row, col)and collect all cells in the connected component. - For every cell in the component, examine its four neighbors.
- If any neighbor is outside the grid or has a different color, mark the cell as a border.
- Recolor all marked border cells.
This approach is actually already efficient enough for the constraints, because the grid is small. However, if implemented naively, it may repeatedly scan cells or perform redundant connectivity checks.
For example, a poorly designed brute force method might run a fresh DFS from every cell to verify connectivity, leading to significantly worse complexity.
Optimal Approach
The key observation is that the problem is fundamentally a graph traversal problem.
The connected component can be explored directly using DFS or BFS starting from (row, col). During traversal, we can determine whether each visited cell is a border.
A cell is a border if:
- It lies on the edge of the matrix
- Or one of its neighbors has a different original color
The optimal solution performs a single traversal of the component and stores border cells separately. After traversal finishes, only those border cells are recolored.
This avoids modifying the grid during traversal, which keeps neighbor checks accurate.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O((m * n)^2) | O(m * n) | May repeatedly recompute connectivity |
| Optimal | O(m * n) | O(m * n) | Single DFS/BFS traversal of component |
Algorithm Walkthrough
Step 1: Store the Original Color
We first save the color of the starting cell:
original_color = grid[row][col]
This is important because every cell in the connected component must match this color.
Step 2: Create Supporting Structures
We maintain:
- A
visitedmatrix to avoid revisiting cells - A list called
bordersto store cells that must be recolored
The visited matrix prevents infinite recursion and redundant work.
Step 3: Start DFS Traversal
We run DFS beginning from (row, col).
For every visited cell:
- Mark it as visited.
- Examine all four neighboring directions.
Step 4: Determine Whether the Cell Is a Border
For each neighbor:
- If the neighbor is outside the grid, the current cell is a border.
- If the neighbor exists but has a different color, the current cell is also a border.
- If the neighbor has the same original color and has not been visited, continue DFS into it.
We use a boolean flag such as is_border.
Step 5: Record Border Cells
After checking all neighbors:
- If
is_borderis true, append the cell coordinates toborders.
We do not recolor immediately because changing values early could interfere with later neighbor checks.
Step 6: Recolor Border Cells
After DFS completes:
- Iterate through all cells stored in
borders - Set each cell to the new color
Step 7: Return the Grid
Finally, return the modified grid.
Why it works
The DFS visits every cell in the connected component exactly once. For each visited cell, we correctly determine whether it belongs to the border by checking adjacency conditions.
Interior cells are never recolored because all four neighbors belong to the same component and remain inside the grid. Border cells are recolored because they either touch the grid boundary or touch a cell outside the component.
Since recoloring happens only after traversal completes, all connectivity checks use the original grid values, guaranteeing correctness.
Python Solution
from typing import List
class Solution:
def colorBorder(
self,
grid: List[List[int]],
row: int,
col: int,
color: int
) -> List[List[int]]:
rows = len(grid)
cols = len(grid[0])
original_color = grid[row][col]
visited = [[False] * cols for _ in range(rows)]
borders = []
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
def dfs(r: int, c: int) -> None:
visited[r][c] = True
is_border = False
for dr, dc in directions:
nr = r + dr
nc = c + dc
# Outside the grid
if nr < 0 or nr >= rows or nc < 0 or nc >= cols:
is_border = True
# Different color neighbor
elif grid[nr][nc] != original_color:
is_border = True
# Continue DFS
elif not visited[nr][nc]:
dfs(nr, nc)
if is_border:
borders.append((r, c))
dfs(row, col)
for r, c in borders:
grid[r][c] = color
return grid
The implementation begins by storing the dimensions of the grid and the original component color. This original color acts as the identifier for the connected component.
A visited matrix is used to ensure each component cell is processed only once. The borders list stores coordinates that should eventually be recolored.
The DFS function recursively explores neighboring cells. For every neighbor, it checks whether the neighbor lies outside the grid or has a different color. Either condition means the current cell belongs to the border.
If a neighboring cell shares the same original color and has not yet been visited, DFS continues into that neighbor.
Importantly, the algorithm does not recolor cells during traversal. Instead, it records border cells first and recolors afterward. This guarantees that all connectivity checks use the original grid state.
Finally, every recorded border cell is updated to the target color, and the modified grid is returned.
Go Solution
func colorBorder(grid [][]int, row int, col int, color int) [][]int {
rows := len(grid)
cols := len(grid[0])
originalColor := grid[row][col]
visited := make([][]bool, rows)
for i := range visited {
visited[i] = make([]bool, cols)
}
borders := make([][2]int, 0)
directions := [][2]int{
{1, 0},
{-1, 0},
{0, 1},
{0, -1},
}
var dfs func(int, int)
dfs = func(r int, c int) {
visited[r][c] = true
isBorder := false
for _, dir := range directions {
nr := r + dir[0]
nc := c + dir[1]
if nr < 0 || nr >= rows || nc < 0 || nc >= cols {
isBorder = true
} else if grid[nr][nc] != originalColor {
isBorder = true
} else if !visited[nr][nc] {
dfs(nr, nc)
}
}
if isBorder {
borders = append(borders, [2]int{r, c})
}
}
dfs(row, col)
for _, cell := range borders {
r := cell[0]
c := cell[1]
grid[r][c] = color
}
return grid
}
The Go implementation follows the same logical structure as the Python version. The main difference is that Go requires explicit allocation of the visited matrix and uses slices instead of Python lists.
The DFS function is implemented as a closure so it can recursively access shared variables like grid, visited, and borders.
Coordinates are stored as fixed-size arrays [2]int, which is a lightweight and efficient representation.
Integer overflow is not a concern because grid dimensions are very small.
Worked Examples
Example 1
Input:
grid = [[1,1],
[1,2]]
row = 0
col = 0
color = 3
The connected component contains:
(0,0), (0,1), (1,0)
DFS Traversal
| Cell | Border Reason | Border? |
|---|---|---|
| (0,0) | Touches top and left edge | Yes |
| (0,1) | Adjacent to value 2 | Yes |
| (1,0) | Touches bottom and left edge | Yes |
Border cells collected:
[(0,0), (0,1), (1,0)]
After recoloring:
[[3,3],
[3,2]]
Example 2
Input:
grid = [[1,2,2],
[2,3,2]]
row = 0
col = 1
color = 3
Connected component:
(0,1), (0,2), (1,2)
Border Detection
| Cell | Border Reason |
|---|---|
| (0,1) | Top edge |
| (0,2) | Top and right edge |
| (1,2) | Right edge |
All component cells are borders.
Final grid:
[[1,3,3],
[2,3,3]]
Example 3
Input:
grid = [[1,1,1],
[1,1,1],
[1,1,1]]
row = 1
col = 1
color = 2
The entire grid forms one component.
Interior vs Border
| Cell | Border? |
|---|---|
| (0,0) | Yes |
| (0,1) | Yes |
| (0,2) | Yes |
| (1,0) | Yes |
| (1,1) | No |
| (1,2) | Yes |
| (2,0) | Yes |
| (2,1) | Yes |
| (2,2) | Yes |
Only the center remains unchanged.
Final grid:
[[2,2,2],
[2,1,2],
[2,2,2]]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m * n) | Each cell is visited at most once |
| Space | O(m * n) | Visited matrix and recursion stack |
The DFS traversal processes each component cell exactly one time. Every neighbor check is constant time, so total work is proportional to the number of grid cells.
The space complexity comes from two sources:
- The visited matrix
- The recursion stack in the worst case where the component contains every cell
Since the grid size is bounded by 50 x 50, this is completely acceptable.
Test Cases
from typing import List
class Solution:
def colorBorder(
self,
grid: List[List[int]],
row: int,
col: int,
color: int
) -> List[List[int]]:
rows = len(grid)
cols = len(grid[0])
original_color = grid[row][col]
visited = [[False] * cols for _ in range(rows)]
borders = []
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
def dfs(r: int, c: int) -> None:
visited[r][c] = True
is_border = False
for dr, dc in directions:
nr = r + dr
nc = c + dc
if nr < 0 or nr >= rows or nc < 0 or nc >= cols:
is_border = True
elif grid[nr][nc] != original_color:
is_border = True
elif not visited[nr][nc]:
dfs(nr, nc)
if is_border:
borders.append((r, c))
dfs(row, col)
for r, c in borders:
grid[r][c] = color
return grid
sol = Solution()
# Example 1
assert sol.colorBorder(
[[1,1],[1,2]],
0,
0,
3
) == [[3,3],[3,2]]
# Example 2
assert sol.colorBorder(
[[1,2,2],[2,3,2]],
0,
1,
3
) == [[1,3,3],[2,3,3]]
# Example 3
assert sol.colorBorder(
[[1,1,1],[1,1,1],[1,1,1]],
1,
1,
2
) == [[2,2,2],[2,1,2],[2,2,2]]
# Single cell grid
assert sol.colorBorder(
[[5]],
0,
0,
9
) == [[9]]
# Component already same color as target
assert sol.colorBorder(
[[1,1],[1,1]],
0,
0,
1
) == [[1,1],[1,1]]
# Interior cell should remain unchanged
assert sol.colorBorder(
[[1,1,1],
[1,1,1],
[1,1,1]],
1,
1,
5
) == [[5,5,5],
[5,1,5],
[5,5,5]]
# Irregular shaped component
assert sol.colorBorder(
[[1,2,1],
[1,1,1],
[2,1,2]],
1,
1,
7
) == [[7,2,7],
[7,7,7],
[2,7,2]]
Test Summary
| Test | Why |
|---|---|
| Example 1 | Basic connected component |
| Example 2 | Component touching multiple boundaries |
| Example 3 | Large component with interior cell |
| Single cell grid | Smallest possible input |
| Same target color | Ensures traversal still works |
| Interior preservation | Confirms non-border cells remain unchanged |
| Irregular shape | Tests complex border detection |
Edge Cases
Single Cell Component
A component containing only one cell can easily be mishandled if the algorithm assumes every cell has same-colored neighbors. In reality, a single isolated cell is always a border because it either touches the grid edge or neighboring cells of different colors.
The implementation handles this naturally because any missing or different neighbor sets is_border = True.
Entire Grid Is One Component
When every cell belongs to the same connected component, only the outer layer should be recolored. The interior cells must remain unchanged.
A buggy implementation might recolor all cells because they all belong to the component. Our border detection logic avoids this by requiring either an out-of-bounds neighbor or a different-colored neighbor before marking a cell as a border.
Recoloring During Traversal
One of the most common mistakes is recoloring cells immediately during DFS. If this happens, later neighbor checks may incorrectly think recolored cells belong to a different component.
This implementation avoids the issue by postponing recoloring until traversal is fully complete. The original grid values remain intact throughout DFS, ensuring accurate connectivity checks.