1034. Coloring A Border
Problem Description
You have an m x n
matrix called grid
where each cell contains an integer representing a color value. You're also given three parameters: row
, col
(coordinates of a starting cell), and color
(a new color value to apply).
The problem involves understanding connected components and their borders:
- Adjacent squares: Two cells that share an edge (up, down, left, or right - not diagonals)
- Connected component: A group of cells that have the same color and are connected through adjacent cells
- Border of a connected component: Cells within the component that either:
- Touch at least one cell with a different color, OR
- Are on the edge of the grid (first/last row or column)
Your task is to:
- Find the connected component that contains the cell at position
grid[row][col]
- Identify all border cells of this component
- Change the color of these border cells to the given
color
- Return the modified grid
For example, if you have a region of cells with the same color, only the cells on the perimeter of that region (those touching different colors or the grid edge) should be recolored, while interior cells remain unchanged.
The solution uses DFS (Depth-First Search) to traverse the connected component starting from grid[row][col]
. During traversal, it marks visited cells and checks each cell's neighbors. If a cell has a neighbor with a different color or is at the grid boundary, it's identified as a border cell and colored with the new color
.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The grid can be viewed as a graph where each cell is a node, and edges connect adjacent cells (up, down, left, right). We need to traverse connected components based on color similarity.
Is it a tree?
- No: The grid structure is not a tree. It's a 2D grid where cells can form cycles when they have the same color, and there's no hierarchical parent-child relationship.
Is the problem related to directed acyclic graphs?
- No: The grid connections are undirected (if cell A is adjacent to cell B, then B is also adjacent to A), and cycles can exist within same-colored regions.
Is the problem related to shortest paths?
- No: We're not finding shortest paths between cells. We're identifying and modifying borders of a connected component.
Does the problem involve connectivity?
- Yes: The core of the problem is finding connected components - groups of cells with the same color that are connected through adjacent cells. We need to explore all cells connected to the starting cell.
Does the problem have small constraints?
- Yes: Grid problems typically have manageable constraints (m Γ n grid), allowing us to visit each cell and explore the connected component efficiently.
DFS/backtracking?
- Yes: DFS is perfect for exploring connected components. Starting from
grid[row][col]
, we recursively visit all adjacent cells with the same color, marking borders as we go.
Conclusion: The flowchart suggests using DFS (Depth-First Search) to traverse the connected component starting from the given cell, identify border cells during traversal, and color them accordingly.
Intuition
When we think about finding and coloring the border of a connected component, we need to first understand what makes a cell a "border cell". A cell is on the border if it's part of our connected component BUT either touches the edge of the grid or touches a cell with a different color.
The natural approach is to explore the entire connected component starting from grid[row][col]
. As we visit each cell, we can check its surroundings to determine if it's a border cell. This exploration pattern naturally fits DFS because:
- We need to visit all cells in the component exactly once
- From each cell, we branch out to its neighbors with the same color
- We need to track which cells we've already visited to avoid infinite loops
The key insight is that we can identify border cells during our DFS traversal. For each cell we visit, we check all four adjacent positions:
- If an adjacent position is out of bounds β current cell is a border
- If an adjacent position has a different color β current cell is a border
- If an adjacent position has the same color and hasn't been visited β continue DFS to that cell
By using a vis
(visited) array, we ensure we don't revisit cells and get stuck in cycles. The beauty of this approach is that we simultaneously:
- Traverse the entire connected component
- Identify which cells are borders
- Color the borders in a single pass
The DFS naturally handles the recursive nature of exploring connected regions - each cell leads us to its same-colored neighbors, which lead to their neighbors, and so on, until we've mapped out the entire component and identified all its border cells.
Learn more about Depth-First Search and Breadth-First Search patterns.
Solution Approach
The implementation uses a DFS (Depth-First Search) approach with a visited tracking mechanism. Let's walk through the key components:
Data Structures:
vis
: A 2D boolean array of the same size as the grid to track visited cells, preventing infinite loops and redundant processing- The original
grid
is modified in-place to store the result
Main Function Setup:
m, n = len(grid), len(grid[0])
vis = [[False] * n for _ in range(m)]
dfs(row, col, grid[row][col])
We initialize the visited array and start DFS from the given starting position, passing the original color of that cell.
DFS Function Logic:
The recursive dfs(i, j, c)
function takes the current position (i, j)
and the original color c
of the connected component.
-
Mark as visited:
vis[i][j] = True
ensures we don't process the same cell twice -
Explore all 4 directions: Using
pairwise((-1, 0, 1, 0, -1))
generates the four direction pairs:(-1, 0)
,(0, 1)
,(1, 0)
,(0, -1)
representing up, right, down, and left movements -
For each adjacent cell
(x, y)
:- If within bounds (
0 <= x < m and 0 <= y < n
):- If not visited:
- If it has the same color (
grid[x][y] == c
): Recursively explore it withdfs(x, y, c)
- If it has a different color: Mark current cell as border by setting
grid[i][j] = color
- If it has the same color (
- If not visited:
- If out of bounds: The current cell is on the grid edge, so mark it as border with
grid[i][j] = color
- If within bounds (
Key Implementation Details:
- The algorithm cleverly identifies border cells by checking their neighbors during traversal
- Border cells are colored immediately when identified
- Non-border cells (interior cells completely surrounded by same-colored cells) remain unchanged
- The
pairwise
function efficiently generates consecutive pairs from the direction array
Time Complexity: O(m Γ n)
where each cell is visited at most once
Space Complexity: O(m Γ n)
for the visited array and O(min(m, n))
for the recursion stack in the worst case
The solution elegantly combines traversal and border detection in a single pass, modifying the grid in-place to achieve the desired result.
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 to illustrate the solution approach.
Initial Setup:
grid = [[1, 2, 2], [2, 3, 2]] row = 0, col = 1, color = 4
We want to find the connected component starting at grid[0][1]
(value = 2) and color its border with color 4.
Step 1: Initialize
- Create visited array:
vis = [[False, False, False], [False, False, False]]
- Start DFS at position (0, 1) with original color c = 2
Step 2: DFS at (0, 1)
- Mark visited:
vis[0][1] = True
- Check all 4 neighbors:
- Up (-1, 1): Out of bounds β Current cell is border! Set
grid[0][1] = 4
- Right (0, 2): In bounds, not visited,
grid[0][2] = 2
(same color) β Continue DFS - Down (1, 1): In bounds, not visited,
grid[1][1] = 3
(different color) β Current cell is border! (already set to 4) - Left (0, 0): In bounds, not visited,
grid[0][0] = 1
(different color) β Current cell is border! (already set to 4)
- Up (-1, 1): Out of bounds β Current cell is border! Set
Step 3: DFS at (0, 2)
- Mark visited:
vis[0][2] = True
- Check all 4 neighbors:
- Up (-1, 2): Out of bounds β Current cell is border! Set
grid[0][2] = 4
- Right (0, 3): Out of bounds β Current cell is border! (already set to 4)
- Down (1, 2): In bounds, not visited,
grid[1][2] = 2
(same color) β Continue DFS - Left (0, 1): In bounds, already visited β Skip
- Up (-1, 2): Out of bounds β Current cell is border! Set
Step 4: DFS at (1, 2)
- Mark visited:
vis[1][2] = True
- Check all 4 neighbors:
- Up (0, 2): In bounds, already visited β Skip
- Right (1, 3): Out of bounds β Current cell is border! Set
grid[1][2] = 4
- Down (2, 2): Out of bounds β Current cell is border! (already set to 4)
- Left (1, 1): In bounds, not visited,
grid[1][1] = 3
(different color) β Current cell is border! (already set to 4)
Final Result:
grid = [[1, 4, 4], [2, 3, 4]]
The connected component of 2's starting at (0, 1) included cells (0, 1), (0, 2), and (1, 2). All three cells were on the border because:
- (0, 1): Touches the top edge and cells with different colors
- (0, 2): Touches the top and right edges
- (1, 2): Touches the bottom and right edges, plus a cell with different color
Notice how the algorithm identifies border cells during traversal by checking each neighbor - if any neighbor is out of bounds or has a different color, the current cell gets colored immediately.
Solution Implementation
1class Solution:
2 def colorBorder(
3 self, grid: List[List[int]], row: int, col: int, color: int
4 ) -> List[List[int]]:
5 """
6 Colors the border of the connected component containing grid[row][col].
7 A cell is on the border if it's part of the component and either:
8 1. On the edge of the grid, or
9 2. Adjacent to a cell with a different color
10
11 Args:
12 grid: 2D grid of colors
13 row: Starting row coordinate
14 col: Starting column coordinate
15 color: New color for the border
16
17 Returns:
18 Modified grid with colored borders
19 """
20
21 def dfs(curr_row: int, curr_col: int, original_color: int) -> None:
22 """
23 Depth-first search to find and color border cells.
24
25 Args:
26 curr_row: Current row position
27 curr_col: Current column position
28 original_color: The original color of the connected component
29 """
30 # Mark current cell as visited
31 visited[curr_row][curr_col] = True
32
33 # Check all 4 adjacent cells (up, right, down, left)
34 directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
35 for dx, dy in directions:
36 next_row = curr_row + dx
37 next_col = curr_col + dy
38
39 # Check if the adjacent cell is within grid bounds
40 if 0 <= next_row < rows and 0 <= next_col < cols:
41 if not visited[next_row][next_col]:
42 if grid[next_row][next_col] == original_color:
43 # Continue DFS for cells with same color
44 dfs(next_row, next_col, original_color)
45 else:
46 # Current cell is on border (adjacent to different color)
47 grid[curr_row][curr_col] = color
48 else:
49 # Current cell is on border (edge of grid)
50 grid[curr_row][curr_col] = color
51
52 # Initialize grid dimensions
53 rows = len(grid)
54 cols = len(grid[0])
55
56 # Initialize visited matrix to track processed cells
57 visited = [[False] * cols for _ in range(rows)]
58
59 # Start DFS from the given starting position
60 original_color = grid[row][col]
61 dfs(row, col, original_color)
62
63 return grid
64
1class Solution {
2 // Instance variables for grid traversal
3 private int[][] grid;
4 private int targetColor;
5 private int rows;
6 private int cols;
7 private boolean[][] visited;
8
9 /**
10 * Colors the border of the connected component containing grid[row][col]
11 * @param grid - 2D integer array representing the grid
12 * @param row - starting row position
13 * @param col - starting column position
14 * @param color - new color to paint the border with
15 * @return modified grid with colored borders
16 */
17 public int[][] colorBorder(int[][] grid, int row, int col, int color) {
18 // Initialize instance variables
19 this.grid = grid;
20 this.targetColor = color;
21 this.rows = grid.length;
22 this.cols = grid[0].length;
23 this.visited = new boolean[rows][cols];
24
25 // Start DFS from the given cell with its original color
26 int originalColor = grid[row][col];
27 dfs(row, col, originalColor);
28
29 return grid;
30 }
31
32 /**
33 * Depth-first search to identify and color border cells
34 * A cell is a border cell if:
35 * 1. It's at the edge of the grid, OR
36 * 2. It's adjacent to a cell with a different color
37 * @param currentRow - current row position
38 * @param currentCol - current column position
39 * @param originalColor - original color of the connected component
40 */
41 private void dfs(int currentRow, int currentCol, int originalColor) {
42 // Mark current cell as visited
43 visited[currentRow][currentCol] = true;
44
45 // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
46 int[] directions = {-1, 0, 1, 0, -1};
47
48 // Check all 4 adjacent cells
49 for (int k = 0; k < 4; k++) {
50 int nextRow = currentRow + directions[k];
51 int nextCol = currentCol + directions[k + 1];
52
53 // Check if the adjacent cell is within grid bounds
54 if (nextRow >= 0 && nextRow < rows && nextCol >= 0 && nextCol < cols) {
55 // If the adjacent cell hasn't been visited
56 if (!visited[nextRow][nextCol]) {
57 // If adjacent cell has the same color, continue DFS
58 if (grid[nextRow][nextCol] == originalColor) {
59 dfs(nextRow, nextCol, originalColor);
60 } else {
61 // Adjacent cell has different color, so current cell is a border
62 grid[currentRow][currentCol] = targetColor;
63 }
64 }
65 } else {
66 // Current cell is at the edge of the grid, so it's a border
67 grid[currentRow][currentCol] = targetColor;
68 }
69 }
70 }
71}
72
1class Solution {
2public:
3 vector<vector<int>> colorBorder(vector<vector<int>>& grid, int row, int col, int color) {
4 int rows = grid.size();
5 int cols = grid[0].size();
6
7 // Track visited cells to avoid infinite recursion
8 bool visited[rows][cols];
9 memset(visited, false, sizeof(visited));
10
11 // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
12 int directions[5] = {-1, 0, 1, 0, -1};
13
14 // DFS function to explore connected component and color borders
15 function<void(int, int, int)> dfs = [&](int currentRow, int currentCol, int originalColor) {
16 // Mark current cell as visited
17 visited[currentRow][currentCol] = true;
18
19 // Check all 4 adjacent cells
20 for (int k = 0; k < 4; ++k) {
21 int nextRow = currentRow + directions[k];
22 int nextCol = currentCol + directions[k + 1];
23
24 // Check if the adjacent cell is within grid boundaries
25 if (nextRow >= 0 && nextRow < rows && nextCol >= 0 && nextCol < cols) {
26 // If adjacent cell hasn't been visited yet
27 if (!visited[nextRow][nextCol]) {
28 // If adjacent cell has same color, continue DFS
29 if (grid[nextRow][nextCol] == originalColor) {
30 dfs(nextRow, nextCol, originalColor);
31 } else {
32 // Adjacent cell has different color, so current cell is on border
33 grid[currentRow][currentCol] = color;
34 }
35 }
36 } else {
37 // Adjacent position is outside grid, so current cell is on border
38 grid[currentRow][currentCol] = color;
39 }
40 }
41 };
42
43 // Start DFS from the given starting position
44 dfs(row, col, grid[row][col]);
45
46 return grid;
47 }
48};
49
1/**
2 * Colors the border of a connected component in a grid
3 * @param grid - 2D array representing the grid
4 * @param row - Starting row position
5 * @param col - Starting column position
6 * @param color - New color to apply to the border
7 * @returns Modified grid with colored borders
8 */
9function colorBorder(grid: number[][], row: number, col: number, color: number): number[][] {
10 const rows: number = grid.length;
11 const cols: number = grid[0].length;
12
13 // Track visited cells to avoid infinite recursion
14 const visited: boolean[][] = new Array(rows)
15 .fill(0)
16 .map(() => new Array(cols).fill(false));
17
18 // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
19 const directions: number[] = [-1, 0, 1, 0, -1];
20
21 /**
22 * Depth-first search to find and color border cells
23 * @param currentRow - Current row position
24 * @param currentCol - Current column position
25 * @param originalColor - Original color of the connected component
26 */
27 const dfs = (currentRow: number, currentCol: number, originalColor: number): void => {
28 // Mark current cell as visited
29 visited[currentRow][currentCol] = true;
30
31 // Check all 4 adjacent cells
32 for (let dirIndex = 0; dirIndex < 4; dirIndex++) {
33 const nextRow: number = currentRow + directions[dirIndex];
34 const nextCol: number = currentCol + directions[dirIndex + 1];
35
36 // Check if next position is within grid bounds
37 if (nextRow >= 0 && nextRow < rows && nextCol >= 0 && nextCol < cols) {
38 // Process unvisited cells
39 if (!visited[nextRow][nextCol]) {
40 if (grid[nextRow][nextCol] === originalColor) {
41 // Continue DFS for cells with same color
42 dfs(nextRow, nextCol, originalColor);
43 } else {
44 // Current cell is on border (adjacent to different color)
45 grid[currentRow][currentCol] = color;
46 }
47 }
48 } else {
49 // Current cell is on grid edge (border of the grid)
50 grid[currentRow][currentCol] = color;
51 }
52 }
53 };
54
55 // Start DFS from the given position with its original color
56 dfs(row, col, grid[row][col]);
57
58 return grid;
59}
60
Time and Space Complexity
Time Complexity: O(m * n)
where m
is the number of rows and n
is the number of columns in the grid.
The algorithm uses depth-first search (DFS) to traverse the connected component starting from (row, col)
. Each cell in the grid is visited at most once due to the vis
array tracking visited cells. For each visited cell, we check at most 4 adjacent cells (up, down, left, right), which is a constant operation. Therefore, the total time complexity is O(m * n)
.
Space Complexity: O(m * n)
The space complexity consists of:
- The
vis
array which stores boolean values for all cells:O(m * n)
- The recursive call stack for DFS in the worst case (when all cells form a single connected component):
O(m * n)
The dominant factor is O(m * n)
for both the visited array and potentially the recursion stack depth, resulting in an overall space complexity of O(m * n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Premature Border Coloring Affecting Subsequent Checks
The Problem:
The current implementation colors border cells immediately when detected. This can cause issues when the new color
is the same as an adjacent component's color. Consider this scenario:
- Original grid has a component with color
1
next to a component with color2
- We want to color the border of the color
1
component with color2
- After coloring some border cells to
2
, subsequent DFS calls might incorrectly identify interior cells as borders because they now appear adjacent to cells with color2
Example:
Initial: After some coloring: Incorrect result: 1 1 2 2 1 2 2 2 2 1 1 2 -> 2 1 2 -> 2 2 2 1 1 2 2 1 2 2 2 2
Solution: Store border cells separately and color them after the entire DFS completes:
def colorBorder(self, grid, row, col, color):
def dfs(curr_row, curr_col, original_color):
visited[curr_row][curr_col] = True
is_border = False
directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
for dx, dy in directions:
next_row = curr_row + dx
next_col = curr_col + dy
if 0 <= next_row < rows and 0 <= next_col < cols:
if not visited[next_row][next_col]:
if grid[next_row][next_col] == original_color:
dfs(next_row, next_col, original_color)
else:
is_border = True
elif grid[next_row][next_col] != original_color:
# Check against original grid, not visited cells
is_border = True
else:
is_border = True
if is_border:
border_cells.add((curr_row, curr_col))
rows, cols = len(grid), len(grid[0])
visited = [[False] * cols for _ in range(rows)]
border_cells = set()
original_color = grid[row][col]
dfs(row, col, original_color)
# Color all border cells after DFS completes
for r, c in border_cells:
grid[r][c] = color
return grid
Pitfall 2: Not Handling Single-Cell Components
The Problem: A connected component might consist of just one cell. If this cell is surrounded by different colors or is on the grid edge, it should be colored. The current implementation handles this correctly, but it's easy to overlook when optimizing.
Example:
Grid: Expected: 2 2 2 2 2 2 2 1 2 -> 2 3 2 (coloring with 3) 2 2 2 2 2 2
Pitfall 3: Incorrect Border Detection for Already-Visited Cells
The Problem: When checking adjacent cells, we need to distinguish between:
- Cells that are part of our component (same original color)
- Cells that are not part of our component (different color)
The current code only checks unvisited cells for different colors, potentially missing border detection when an already-visited cell of the same component is adjacent to a cell with a different color.
Solution:
The improved solution above handles this by checking grid[next_row][next_col] != original_color
even for visited cells, ensuring proper border detection throughout the component.
In a binary min heap, the minimum element can be found in:
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
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
Want a Structured Path to Master System Design Too? Donβt Miss This!