1034. Coloring A Border
Problem Description
You are given a matrix of integers where each integer represents a color. You need to identify a connected component in the matrix based on a starting cell given by its row and column indexes. A connected component entails all adjacent cells (up, down, left, or right) with the same color. The border of this connected component includes all cells in the component adjacent to cells with different colors or cells lying on the grid's boundary.
Your task is to recolor only the cells on the border of this connected component with a new color. The index of the starting cell within the connected component and the new color are given. The updated grid should be returned, showing the new border color while leaving the interior of the connected component and the rest of the grid unchanged.
Flowchart Walkthrough
For leetcode 1034. Coloring A Border, let's analyze the problem using the Flowchart. Here's how we'd follow the steps to determine the best approach:
-
Is it a graph?
- Yes: The grid can be seen as a graph where each cell is a node, connected to its adjacent neighbors.
-
Is it a tree?
- No: The grid structure allows cells to create cycles and isn't necessarily hierarchical.
-
Is the problem related to directed acyclic graphs (DAGs)?
- No: The focus here is coloring the border of a connected component, not processing elements in a directed acyclic order.
-
Is the problem related to shortest paths?
- No: We are not seeking the shortest path; we need to identify and modify the boundary of a connected region.
-
Does the problem involve connectivity?
- Yes: The task is to find the boundary of a connected component that matches a given condition.
-
Does the problem have small constraints?
- Yes: We deal with a grid (typically small in competitive programming contexts), suggesting that DFS/backtracking might be efficient enough.
Since we reach the node "DFS/backtracking" from "Does the problem have small constraints?" and answer positively, the recommended algorithm is Depth-First Search (DFS) for solving this problem.
Conclusion: Using Depth-First Search (DFS) is ideal for traversing the connected component, identifying border cells, and modifying them as required.
Intuition
The solution to this problem uses Depth-First Search (DFS), an algorithm well-suited for exploring and marking the structure of a connected component within a grid. We begin at a cell (grid[row][col]
) and look for all adjacent cells that share the same color, which signifies that they are part of the same connected component.
The intuition behind using DFS is to traverse through all the cells in the connected component starting from the specified cell (grid[row][col]
). As we do this, we need to determine which cells are on the border of the component. A border cell is defined as a cell that:
- Is on the edge of the grid, or
- Is adjacent to a cell that has a different color than the one we started with.
The DFS function checks all four directions around a cell. For every cell that we visit:
- If the cell is within the grid and the adjacent cell is not visited yet, we check:
- If the adjacent cell has the same color, we continue DFS on that cell since it's part of the same component.
- If the adjacent cell has a different color, we know that we are on the border, and thus we change the color of the current cell.
- If the cell is on the edge of the grid, it's a border cell, so we change the color of the current cell.
We iterate in this manner until all the cells in the connected component have been visited and the appropriate border cells have been recolored. An auxiliary vis
matrix is used to keep track of visited cells to avoid processing a cell more than once, which helps in preventing an infinite loop.
Finally, we return the modified grid with the newly colored borders, while the interior of the connected component and all other parts of the grid remain unchanged.
Learn more about Depth-First Search and Breadth-First Search patterns.
Solution Approach
The solution utilizes a Depth-First Search (DFS) approach to explore the grid and identify border cells of the connected component. The following are the key aspects of the implementation:
-
DFS Method: The solution defines a nested
dfs
function which is responsible for the recursive search. It accepts the current cell's indicesi
andj
, as well as the original colorc
of the connected component. -
Tracking Visited Cells: A two-dimensional list
vis
is created with the same dimensions as the input gridgrid
, initialized withFalse
values. This list is used to keep track of cells that have already been visited to prevent infinite recursion and repeated processing. -
Checking Neighbors: In each call to the
dfs
function, it iterates over the four adjacent cells (up, down, left, right) using a for-loop and thepairwise
helper to generate pairs of directions. -
Edge Conditions: The algorithm checks if a neighboring cell is within the boundaries of the grid. If it is outside, it means the current cell is on the border of the grid and thus should be recolored.
-
Coloring Border Cells:
- If the adjacent cell is within the grid and hasn't been visited (
vis[x][y]
isFalse
), there are two conditions:- If the adjacent cell has the same color as the original (
grid[x][y] == c
), thedfs
function is called recursively for that cell to continue the component traversal. - If the adjacent cell has a different color, the current cell is on the border of the connected component and its color is changed to
color
.
- If the adjacent cell has the same color as the original (
- If the adjacent cell is within the grid and hasn't been visited (
-
Calling DFS: After initializing the
vis
grid, thedfs
function is invoked starting from the cellgrid[row][col]
, using its color as the reference for the connected component. -
Returning the Result: Once the DFS traversal is complete and all border cells are recolored, the modified grid is returned.
The implementation creatively uses recursion to deeply explore the connected component and the auxiliary vis
matrix to manage state across recursive calls. The determination of border cells is carried out in a granular fashion by checking each neighbor, which effectively allows the coloring to occur only where it is required.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small 4x4 matrix and walk through the solution approach with a given starting cell and new color:
Grid: 1 1 2 3 1 1 2 4 5 1 3 4 6 5 5 4 Starting Cell: (1, 1) (Zero-indexed, meaning the cell in row 1, column 1) Original Color of Starting Cell: 1 New Border Color: 9
First, we initialize an auxiliary 2D list vis
of the same size as the grid with False
values to keep track of whether a cell has been visited. Next, we define a dfs
function to explore the grid starting from the given cell.
-
We call the
dfs
function on the starting cell(1, 1)
with its color1
. Since it matches the original color, we continue with DFS. Thevis
grid will be updated withTrue
at this cell to denote it's been visited. -
The
dfs
function now checks adjacent cells(1, 0)
,(1, 2)
,(0, 1)
, and(2, 1)
. -
Whenever visiting an adjacent cell, the border conditions are checked to ensure we are within the grid. Cells at
(1, 0)
and(1, 2)
have the same color as the original and are within the grid; they're not border cells, and thedfs
is called on them. -
Cells
(0, 1)
and(2, 1)
are also part of the connected component, so DFS continues. Meanwhile,vis
is continuously updated. -
By the time we check
(0, 1)
, we find that the cell(0, 0)
is of a different color. This means(0, 1)
is a border cell because it's adjacent to a differently colored cell. -
The cells on the actual edge of the matrix are by default border cells, hence their colors are changed to the new border color
9
. -
As we continue this process recursively, all border cells are identified and changed. For this example, the border consists of cells
(1, 0)
,(0, 1)
,(1, 2)
, and(2, 1)
. -
After all recursion steps complete, the grid is updated as follows:
1 9 2 3 9 1 9 4 5 9 3 4 6 5 5 4
- The
dfs
call terminates, and we return the modified grid with the borders recolored to9
.
The steps in the dfs
ensure that all cells that are in the connected component of the original color are explored, but only the actual border cells are recolored as per the conditions. This is effective at preserving the original structure of the component while updating the border as specified.
Solution Implementation
1from typing import List
2
3class Solution:
4 def colorBorder(
5 self, grid: List[List[int]], row: int, col: int, color: int
6 ) -> List[List[int]]:
7 # Helper function to implement depth-first search
8 def dfs(i: int, j: int, current_color: int) -> None:
9 # Mark the current cell as visited
10 visited[i][j] = True
11 # Iterate through the four possible directions up, right, down, left
12 for direction in range(4):
13 x, y = i + dir_x[direction], j + dir_y[direction]
14 # Check if x and y are within the grid boundaries
15 if 0 <= x < rows and 0 <= y < cols:
16 # If the neighbor has not been visited and has the same color, perform dfs on it
17 if not visited[x][y]:
18 if grid[x][y] == current_color:
19 dfs(x, y, current_color)
20 else:
21 # If the neighbor is of a different color, color the border
22 grid[i][j] = color
23 else:
24 # If the cell is at the boundary, also color the border
25 grid[i][j] = color
26
27 # Number of rows and columns in the grid
28 rows, cols = len(grid), len(grid[0])
29 # Initialize a 2D list to track visited cells
30 visited = [[False] * cols for _ in range(rows)]
31 # Directions for up, right, down, left
32 dir_x = [-1, 0, 1, 0]
33 dir_y = [0, 1, 0, -1]
34 # Start the depth-first search from the given row and col
35 dfs(row, col, grid[row][col])
36 # Return the updated grid after coloring the border
37 return grid
38
1class Solution {
2 private int[][] grid; // Represents the grid of colors.
3 private int newColor; // The color to be used for the border.
4 private int rows; // Number of rows in the grid.
5 private int cols; // Number of columns in the grid.
6 private boolean[][] visited; // To keep track of visited cells during DFS.
7
8 public int[][] colorBorder(int[][] grid, int row, int col, int color) {
9 this.grid = grid;
10 this.newColor = color;
11 rows = grid.length;
12 cols = grid[0].length;
13 visited = new boolean[rows][cols];
14
15 // Perform DFS starting from the given cell.
16 dfs(row, col, grid[row][col]);
17 return grid;
18 }
19
20 private void dfs(int i, int j, int originalColor) {
21 visited[i][j] = true; // Mark the current cell as visited.
22 int[] directions = {-1, 0, 1, 0, -1}; // To traverse in the 4 possible directions: up, right, down, left.
23
24 // Check if the current cell is a border cell and should be colored.
25 boolean isBorderCell = i == 0 || i == rows - 1 || j == 0 || j == cols - 1;
26
27 for (int k = 0; k < 4; ++k) {
28 int x = i + directions[k], y = j + directions[k + 1];
29 if (x >= 0 && x < rows && y >= 0 && y < cols) {
30 // If the neighboring cell has not been visited and has the original color, continue DFS.
31 if (!visited[x][y]) {
32 if (grid[x][y] == originalColor) {
33 dfs(x, y, originalColor);
34 } else {
35 // If the neighboring cell has a different color, this is a border cell.
36 isBorderCell = true;
37 }
38 }
39 } else {
40 // If the cell is on the grid edge, it is a border cell.
41 isBorderCell = true;
42 }
43 }
44
45 // If the current cell is a border cell, color it with the new color.
46 if (isBorderCell) {
47 grid[i][j] = newColor;
48 }
49 }
50}
51
1#include <vector>
2#include <functional>
3#include <cstring>
4
5using namespace std;
6
7class Solution {
8public:
9 vector<vector<int>> colorBorder(vector<vector<int>>& grid, int row, int col, int color) {
10 int numRows = grid.size(); // number of rows in the grid
11 int numCols = grid[0].size(); // number of columns in the grid
12
13 // Define a visited matrix to track visited cells
14 bool visited[numRows][numCols];
15 memset(visited, false, sizeof(visited));
16
17 // Direction arrays for traversing the four neighbors of a cell (up, right, down, left)
18 int directions[5] = {-1, 0, 1, 0, -1};
19
20 // Recursive Depth-First Search (DFS) lambda function
21 function<void(int, int, int)> dfs = [&](int i, int j, int prevColor) {
22 visited[i][j] = true; // Mark the current cell as visited
23 for (int k = 0; k < 4; ++k) { // Loop through each neighbor
24 int nextRow = i + directions[k];
25 int nextCol = j + directions[k + 1];
26 if (nextRow >= 0 && nextRow < numRows && nextCol >= 0 && nextCol < numCols) {
27 if (!visited[nextRow][nextCol]) {
28 if (grid[nextRow][nextCol] == prevColor) {
29 // Recur if the neighboring cell has the same color
30 dfs(nextRow, nextCol, prevColor);
31 } else {
32 // If color is different, color the border
33 grid[i][j] = color;
34 }
35 }
36 } else {
37 // If it's the border of the grid, color it
38 grid[i][j] = color;
39 }
40 }
41 };
42
43 // Start DFS from the given cell (row, col)
44 dfs(row, col, grid[row][col]);
45
46 // Return the updated grid
47 return grid;
48 }
49};
50
1// Function to color the border of a connected component in the grid.
2function colorBorder(grid: number[][], row: number, col: number, newColor: number): number[][] {
3 const rowCount = grid.length; // Number of rows in the grid.
4 const colCount = grid[0].length; // Number of columns in the grid.
5 const visited = new Array(rowCount).fill(0).map(() => new Array(colCount).fill(false)); // Visited cells tracker.
6
7 // Directions for traversing up, right, down, and left.
8 const directions = [-1, 0, 1, 0, -1];
9
10 // Depth-First Search to find and color the border.
11 function depthFirstSearch(x: number, y: number, originColor: number): void {
12 visited[x][y] = true; // Mark the current cell as visited.
13
14 // Explore all four directions from the current cell.
15 for (let i = 0; i < 4; ++i) {
16 const nextX = x + directions[i];
17 const nextY = y + directions[i + 1];
18
19 if (nextX >= 0 && nextX < rowCount && nextY >= 0 && nextY < colCount) {
20 if (!visited[nextX][nextY]) {
21 if (grid[nextX][nextY] == originColor) {
22 // Continue the DFS if the next cell has the same color.
23 depthFirstSearch(nextX, nextY, originColor);
24 } else {
25 // Color the current cell if the next cell has a different color.
26 grid[x][y] = newColor;
27 }
28 }
29 } else {
30 // Color the current cell if it is on the border of the grid.
31 grid[x][y] = newColor;
32 }
33 }
34 }
35
36 // Color the connected component's border starting from the given cell.
37 depthFirstSearch(row, col, grid[row][col]);
38
39 // Apply the new color to any cell that is marked as visited but not colored yet, indicating it's on the border.
40 for (let i = 0; i < rowCount; ++i) {
41 for (let j = 0; j < colCount; ++j) {
42 // A border cell will be visited and have at least one neighboring cell with a different color or be at the grid's edge.
43 if (visited[i][j] && (i == 0 || i == rowCount - 1 || j == 0 || j == colCount - 1 ||
44 (grid[i - 1][j] != grid[i][j]) ||
45 (grid[i + 1][j] != grid[i][j]) ||
46 (grid[i][j - 1] != grid[i][j]) ||
47 (grid[i][j + 1] != grid[i][j]))) {
48 grid[i][j] = newColor;
49 }
50 }
51 }
52
53 // Return the modified grid.
54 return grid;
55}
56
Time and Space Complexity
The provided Python code performs a depth-first search (DFS) to color the borders of a connected component in a grid based on the given conditions. The complexity analysis is as follows:
Time Complexity:
The time complexity of this DFS algorithm is mainly determined by the number of cells in the grid that are visited. In the worst case, the algorithm will visit every cell in the grid once. There are m * n
cells, where m
is the number of rows and n
is the number of columns in the grid. Therefore, the DFS will have a time complexity of O(m * n)
.
Space Complexity:
The space complexity includes the memory taken by the vis
matrix to keep track of visited cells, as well as the recursion call stack for DFS. The vis
matrix has the same dimensions as the input grid, so it takes O(m * n)
space.
The recursion depth can go as deep as the maximum size of the connected component in the grid. In the worst case, this could be the entire grid, which would mean that the call stack can potentially grow up to m * n
calls deep. Hence, the space complexity due to the recursion stack could also be O(m * n)
.
Therefore, the overall space complexity is O(m * n)
as well.
Note: The provided code contains a pairwise
function which is not defined within the code snippet or as part of the standard Python library up to the knowledge cutoff date. Assuming it's intended to generate pairs of coordinates for traversing adjacent cells, its behavior seems to be that of incorrectly iterating over the grid coordinates without actually yielding pairs.
Learn more about how to find time and space complexity quickly using problem constraints.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
https algomonster s3 us east 2 amazonaws com 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
https algomonster s3 us east 2 amazonaws com 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
LeetCode 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 we
Want a Structured Path to Master System Design Too? Don’t Miss This!