Facebook Pixel

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:

  1. Find the connected component that contains the cell at position grid[row][col]
  2. Identify all border cells of this component
  3. Change the color of these border cells to the given color
  4. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. We need to visit all cells in the component exactly once
  2. From each cell, we branch out to its neighbors with the same color
  3. 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:

  1. Traverse the entire connected component
  2. Identify which cells are borders
  3. 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.

  1. Mark as visited: vis[i][j] = True ensures we don't process the same cell twice

  2. 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

  3. 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 with dfs(x, y, c)
        • If it has a different color: Mark current cell as border by setting grid[i][j] = color
    • If out of bounds: The current cell is on the grid edge, so mark it as border with grid[i][j] = color

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 Evaluator

Example 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)

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

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 color 2
  • We want to color the border of the color 1 component with color 2
  • 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 color 2

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:

  1. Cells that are part of our component (same original color)
  2. 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the minimum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More