Coloring A Border
MedPrompt
You are given an m x n integer matrix grid, and three integers row, col, and color. Each value in the grid represents the color of the grid square at that location.
Two squares are called adjacent if they are next to each other in any of the 4 directions.
Two squares belong to the same connected component if they have the same color and they are adjacent.
The border of a connected component is all the squares in the connected component that are either adjacent to (at least) a square not in the component, or on the boundary of the grid (the first or last row or column).
You should color the border of the connected component that contains the square grid[row][col] with color.
Return the final grid.
Example 1:
Input: grid = [[1,1],[1,2]], row = 0, col = 0, color = 3
Output: [[3,3],[3,2]]Example 2:
Input: grid = [[1,2,2],[2,3,2]], row = 0, col = 1, color = 3
Output: [[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
Output: [[2,2,2],[2,1,2],[2,2,2]]
Constraints:
m == grid.lengthn == grid[i].length1 <= m, n <= 501 <= grid[i][j], color <= 10000 <= row < m0 <= col < n
Approaches
2 approaches with complexity analysis and trade-offs.
This approach tackles the problem by breaking it down into two distinct, sequential phases. First, it performs a graph traversal, such as Breadth-First Search (BFS) or Depth-First Search (DFS), to find all the cells that belong to the same connected component as the starting cell (row, col). These cells are collected into a list. In the second phase, the algorithm iterates through this list of component cells to determine which ones qualify as border cells based on the problem's definition. Finally, these identified border cells are colored with the new color.
Algorithm
- Initialization:
- Get the dimensions of the grid,
mandn. - Store the color of the starting cell
(row, col)asoriginalColor. - If
originalColoris the same as the newcolor, return the grid as no changes are needed. - Initialize a 2D boolean array
visitedof sizem x nto keep track of visited cells. - Initialize a queue for BFS and a list
componentCellsto store all cells of the component.
- Get the dimensions of the grid,
- Pass 1: Find the Connected Component
- Add the starting cell
(row, col)to the queue and mark it as visited. - Perform a standard BFS. While the queue is not empty, dequeue a cell.
- Add the dequeued cell to
componentCells. - For each of its four neighbors, if the neighbor is within grid bounds, has the
originalColor, and has not been visited, mark it as visited and enqueue it.
- Add the starting cell
- Pass 2: Identify and Store Border Cells
- Initialize a new list
borderCells. - Iterate through each cell
(r, c)incomponentCells. - For each cell, determine if it's a border cell. A cell is on the border if it's on the physical edge of the grid (
r=0, r=m-1, c=0, or c=n-1) or if it is adjacent to any cell with a color different fromoriginalColor. - If the cell is identified as a border cell, add its coordinates to
borderCells.
- Initialize a new list
- Coloring
- Iterate through the
borderCellslist. - For each cell coordinate in the list, update the corresponding cell in the input
gridwith the newcolor.
- Iterate through the
- Return the modified
grid.
Walkthrough
The core idea is to separate the concern of finding the component from the concern of identifying its border. This makes the logic straightforward and easier to follow.
Phase 1: Component Discovery
A standard BFS/DFS starts from (row, col). The traversal explores all reachable cells that share the same initial color. A visited array is used to prevent cycles and redundant processing. All discovered cells of the component are stored in a list, for example, componentCells.
Phase 2: Border Identification and Coloring
Once the entire component is known, the algorithm iterates through each cell in componentCells. For each cell, it checks if it meets the border criteria: being on the grid's physical boundary or being adjacent to a cell of a different color. The coordinates of cells that satisfy this condition are stored in another list, borderCells. After checking all component cells, a final loop goes through borderCells and applies the new color to the grid.
import java.util.ArrayList;import java.util.LinkedList;import java.util.List;import java.util.Queue; class Solution { public int[][] colorBorder(int[][] grid, int row, int col, int color) { int m = grid.length; int n = grid[0].length; int originalColor = grid[row][col]; if (originalColor == color) { return grid; } Queue<int[]> queue = new LinkedList<>(); boolean[][] visited = new boolean[m][n]; List<int[]> componentCells = new ArrayList<>(); // Pass 1: Find all cells in the connected component queue.offer(new int[]{row, col}); visited[row][col] = true; int[] dr = {-1, 1, 0, 0}; int[] dc = {0, 0, -1, 1}; while (!queue.isEmpty()) { int[] cell = queue.poll(); componentCells.add(cell); for (int i = 0; i < 4; i++) { int nr = cell[0] + dr[i]; int nc = cell[1] + dc[i]; if (nr >= 0 && nr < m && nc >= 0 && nc < n && !visited[nr][nc] && grid[nr][nc] == originalColor) { visited[nr][nc] = true; queue.offer(new int[]{nr, nc}); } } } // Pass 2: Identify border cells from the component List<int[]> borderCells = new ArrayList<>(); for (int[] cell : componentCells) { int r = cell[0]; int c = cell[1]; if (r == 0 || r == m - 1 || c == 0 || c == n - 1) { borderCells.add(cell); continue; } for (int i = 0; i < 4; i++) { int nr = r + dr[i]; int nc = c + dc[i]; if (grid[nr][nc] != originalColor) { borderCells.add(cell); break; } } } // Color the border cells for (int[] cell : borderCells) { grid[cell[0]][cell[1]] = color; } return grid; }}Complexity
Time
O(M * N), where M and N are the grid dimensions. The first pass (BFS/DFS) visits each cell at most once, taking O(M * N) time in the worst case. The second pass iterates through the component cells (at most M*N) and performs a constant number of checks for each. The final coloring step is proportional to the number of border cells. Thus, the total time complexity is dominated by the grid traversal.
Space
O(M * N), where M and N are the grid dimensions. The space is dominated by the `visited` array, the `componentCells` list, and the BFS queue, all of which can grow to the size of the grid in the worst-case scenario.
Trade-offs
Pros
The separation of concerns makes the code very clear and easy to reason about.
Debugging is simpler as each phase can be verified independently.
Cons
Requires extra space to store all cells of the component, which can be large.
Iterates over the component cells multiple times (once to find them, once to check for borders), making it slightly less performant than a single-pass solution.
This optimized approach integrates the component traversal and border identification into a single, efficient pass. Using a graph traversal algorithm like BFS or DFS, we explore the connected component starting from (row, col). For each cell we visit, we immediately check if it qualifies as a border cell. The coordinates of any identified border cells are stored. After the traversal completes, a final step applies the new color to all the stored border cell locations.
Algorithm
- Initialization:
- Get grid dimensions
m,n, and theoriginalColorfromgrid[row][col]. - If
originalColor == color, return the grid immediately. - Initialize a queue for BFS, a
visited2D array, and a listborderCellsto store coordinates of border cells.
- Get grid dimensions
- Single Pass: Traversal and Border Identification
- Add the starting cell
(row, col)to the queue and mark it as visited. - Begin the BFS loop. While the queue is not empty, dequeue a cell
(r, c). - For the current cell
(r, c), check if it's a border cell. This is done by inspecting its four neighbors:- A neighbor that is out of the grid bounds makes
(r, c)a border cell. - An in-bounds neighbor with a color different from
originalColoralso makes(r, c)a border cell.
- A neighbor that is out of the grid bounds makes
- If
(r, c)is determined to be a border cell, add its coordinates to theborderCellslist. - Simultaneously, during the neighbor check, if an in-bounds neighbor has the
originalColorand has not been visited, mark it as visited and add it to the queue to continue the traversal.
- Add the starting cell
- Coloring:
- After the traversal is complete, iterate through the
borderCellslist. - For each coordinate pair, update the
gridat that position with the newcolor.
- After the traversal is complete, iterate through the
- Return the modified
grid.
Walkthrough
The key to this approach is to perform the border check for a cell at the same time it is being processed by the traversal algorithm. This avoids the need to store all component cells and re-iterate over them.
When the BFS algorithm processes a cell (r, c) from its queue, it examines all four of its neighbors. This examination serves two purposes:
- Traversal: To find other cells belonging to the component. If a neighbor has the same
originalColorand hasn't been visited, it's added to the queue for future processing. - Border Detection: To determine if the current cell
(r, c)is on the border. If any neighbor is outside the grid's boundaries or has a color different fromoriginalColor, it signifies that(r, c)is a border cell.
We collect all such border cells in a list. It's important to delay the actual coloring until the entire traversal is finished. Modifying the grid's colors mid-traversal would corrupt the originalColor checks for subsequent cells.
import java.util.ArrayList;import java.util.LinkedList;import java.util.List;import java.util.Queue; class Solution { public int[][] colorBorder(int[][] grid, int row, int col, int color) { int m = grid.length, n = grid[0].length; int originalColor = grid[row][col]; if (originalColor == color) { return grid; } List<int[]> borderCells = new ArrayList<>(); Queue<int[]> queue = new LinkedList<>(); boolean[][] visited = new boolean[m][n]; queue.offer(new int[]{row, col}); visited[row][col] = true; int[] dr = {-1, 1, 0, 0}; int[] dc = {0, 0, -1, 1}; while (!queue.isEmpty()) { int[] cell = queue.poll(); int r = cell[0], c = cell[1]; boolean isBorder = false; // Explore neighbors to find next cells for traversal AND check if current cell is a border for (int i = 0; i < 4; i++) { int nr = r + dr[i]; int nc = c + dc[i]; if (nr >= 0 && nr < m && nc >= 0 && nc < n) { // If neighbor has the same color and is not visited, add to queue if (grid[nr][nc] == originalColor) { if (!visited[nr][nc]) { visited[nr][nc] = true; queue.offer(new int[]{nr, nc}); } } else { // Neighbor has a different color, so current cell is a border isBorder = true; } } else { // Neighbor is out of bounds, so current cell is a border isBorder = true; } } if (isBorder) { borderCells.add(cell); } } for (int[] cell : borderCells) { grid[cell[0]][cell[1]] = color; } return grid; }}Complexity
Time
O(M * N). The algorithm visits each cell in the grid at most once. For each cell, it performs a constant number of operations (checking four neighbors). Therefore, the time complexity is linear with respect to the number of cells in the grid.
Space
O(M * N). In the worst-case scenario (e.g., a checkerboard pattern), the number of border cells can be proportional to the grid size. The `visited` array and the BFS queue also contribute O(M * N) space.
Trade-offs
Pros
More efficient as it processes each component cell only once.
Can be more memory-efficient as it only needs to store border cells, not all component cells.
Cons
The logic within the main traversal loop is more complex as it combines traversal with border detection.
Still requires O(M * N) auxiliary space in the worst case for the visited set and traversal queue.
Solutions
Solution
class Solution {private int[][] grid;private int color;private int m;private int n;private boolean[][] vis;public int[][] colorBorder(int[][] grid, int row, int col, int color) { this.grid = grid; this.color = color; m = grid.length; n = grid[0].length; vis = new boolean[m][n]; dfs(row, col, grid[row][col]); return grid; }private void dfs(int i, int j, int c) { vis[i][j] = true; int[] dirs = {-1, 0, 1, 0, -1}; for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < m && y >= 0 && y < n) { if (!vis[x][y]) { if (grid[x][y] == c) { dfs(x, y, c); } else { grid[i][j] = color; } } } else { grid[i][j] = color; } } }}class Solution {public: vector<vector<int>> colorBorder(vector<vector<int>> &grid, int row, int col, int color) { int m = grid.size(); int n = grid[0].size(); bool vis[m][n]; memset(vis, false, sizeof(vis)); int dirs[5] = {-1, 0, 1, 0, -1}; function<void(int, int, int)> dfs = [&](int i, int j, int c) { vis[i][j] = true; for (int k = 0; k < 4; ++k) { int x = i + dirs[k]; int y = j + dirs[k + 1]; if (x >= 0 && x < m && y >= 0 && y < n) { if (!vis[x][y]) { if (grid[x][y] == c) { dfs(x, y, c); } else { grid[i][j] = color; } } } else { grid[i][j] = color; } } }; dfs(row, col, grid[row][col]); return grid; }};class Solution: def colorBorder(self, grid: List[List[int]], row: int, col: int, color: int) -> List[List[int]]: def dfs(i: int, j: int, c: int) -> None: vis[i][j] = True for a, b in pairwise((- 1, 0, 1, 0, - 1)): x, y = i + a, j + b if 0 <= x < m and 0 <= y < n: if not vis[x][y]: if grid[x][y] == c: dfs(x, y, c) else: grid[i][j] = color else: grid[i][j] = color m, n = len(grid), len(grid[0]) vis = [[False] * n for _ in range(m)] dfs(row, col, grid[row][col]) return gridVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.