Coloring a Border
Coloring a Border
🧩 Problem Statement
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 square at that location.
Two squares belong to the same connected component if they have the same color and are next to each other in any of the 4 directions.
The border of a connected component is all the squares in the connected component that are either:
- On the boundary of the grid (the first or last row or column), or
- Adjacent to a square not belonging to the connected component.
You should color the border of the connected component that contains the squaregrid[row][col]with the givencolor.
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]]
🔍 Approaches
1. DFS / BFS with Border Check
- Identify Component: Start DFS/BFS from
(row, col)to traverse the connected component of the original coloroldColor. - Determine Border: For each visited cell in the component:
- Check if it is on the grid boundary.
- Check if any of its 4 neighbors has a color different from
oldColor. (Note: We must handle visited cells correctly so we don't count "visitedoldColor" as "different color").
- Coloring:
- If a cell is a border, add it to a list of
borders. - After traversal, update all cells in
borderstonewColor. - Alternative: We can modify in-place if we use a
visitedset to distinguish between "processed" and "original color".
Algorithm Steps:
oldColor = grid[row][col].visitedset to track visited cells.borderslist to store border cells.- DFS(r, c):
- Mark
(r, c)as visited. isBorder = false.- Check 4 neighbors:
- If neighbor out of bounds ->
isBorder = true. - Else if neighbor is
oldColorand not visited ->DFS(neighbor). - Else if neighbor is NOT
oldColorand (not visited or visited) ->isBorder = true. (Wait, if it's visited, it MUST beoldColorbecause we only visitoldColorcells. So we only care if neighbor is notoldColorand not visited?) - Correction: A cell is a border if it touches the outside OR touches a cell of a different color.
- Iterate 4 directions.
- If neighbor out of bounds, current
(r, c)is border. - If neighbor is different color (and not visited? No, different color is sufficient),
(r, c)is border. - Recurse on neighbors that are
oldColorand not visited.
- Apply colors to
borders.
⏳ Time & Space Complexity
- Time Complexity: $O(M \cdot N)$. We visit each cell of the component once.
- Space Complexity: $O(M \cdot N)$ for recursion/visited set.
🚀 Code Implementations
C++
#include <vector>
#include <cmath>
using namespace std;
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();
int oldColor = grid[row][col];
vector<vector<bool>> visited(m, vector<bool>(n, false));
vector<pair<int, int>> borders;
dfs(grid, row, col, oldColor, visited, borders, m, n);
for(auto& p : borders) {
grid[p.first][p.second] = color;
}
return grid;
}
private:
void dfs(vector<vector<int>>& grid, int r, int c, int oldColor,
vector<vector<bool>>& visited, vector<pair<int, int>>& borders, int m, int n) {
visited[r][c] = true;
bool isBorder = false;
int dirs[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
for(auto& d : dirs) {
int nr = r + d[0];
int nc = c + d[1];
if(nr < 0 || nr >= m || nc < 0 || nc >= n) {
isBorder = true;
} else if(!visited[nr][nc] && grid[nr][nc] == oldColor) {
dfs(grid, nr, nc, oldColor, visited, borders, m, n);
} else if(grid[nr][nc] != oldColor && !visited[nr][nc]) {
// Neighbor is diff color (and unvisited logic ensures it's truly original diff color)
isBorder = true;
} else if (visited[nr][nc] && grid[nr][nc] != oldColor) {
// Previously visited but changed? (Wait, we update AFTER. So grid[nr][nc] is still oldColor)
// Actually, if we update AFTER, then grid value is stable.
// Neighbor is visited. It MUST be oldColor. So this case (visited && != oldColor) is impossible if we batch update.
}
}
// Simpler Border Check:
// A cell is border if:
// 1. On boundary
// 2. Has a neighbor with Diff Color (Original)
// Since we update at the end, grid[nr][nc] is reliable.
// We just need to check if neighbor is same color.
// Re-logic:
// Count neighbors that are part of the component (oldColor).
// If count < 4, it means at least one neighbor is out-of-bounds OR diff color.
// Thus, use count.
}
};
// ... Wait, let's refine the logic for cleanliness.
Refined Algorithm:
DFS(r, c)visits connectedoldColorcells.- For each
(r, c), check 4 neighbors. - If neighbor is out of bounds OR
grid[neighbor]!=oldColor, then(r, c)is border. - BUT, allow DFS to continue to unvisited
oldColorneighbors. - If
(r, c)is border, add to list.
Refined C++
#include <vector>
using namespace std;
class Solution {
public:
vector<vector<int>> colorBorder(vector<vector<int>>& grid, int row, int col, int color) {
int oldColor = grid[row][col];
int m = grid.size();
int n = grid[0].size();
vector<vector<bool>> visited(m, vector<bool>(n, false));
vector<pair<int, int>> borders;
dfs(grid, row, col, oldColor, visited, borders);
for(auto& p : borders) {
grid[p.first][p.second] = color;
}
return grid;
}
private:
void dfs(vector<vector<int>>& grid, int r, int c, int oldColor,
vector<vector<bool>>& visited, vector<pair<int, int>>& borders) {
visited[r][c] = true;
int m = grid.size();
int n = grid[0].size();
bool isBorder = false;
int dirs[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
for(auto& d : dirs) {
int nr = r + d[0];
int nc = c + d[1];
if(nr < 0 || nr >= m || nc < 0 || nc >= n || (!visited[nr][nc] && grid[nr][nc] != oldColor)) {
// Boundary or Different Color (Unvisited to check original state, but actually if we don't mitigate, visited might be tricky...
// Ah, we use `visited` to track traversal. `grid` is untouched.
// So grid[nr][nc] != oldColor is sufficient check for original color diff.)
isBorder = true;
} else if(!visited[nr][nc] && grid[nr][nc] == oldColor) {
dfs(grid, nr, nc, oldColor, visited, borders);
}
// Wait, what if neighbor is visited? It is part of component. It implies THIS direction is NOT a border.
// But we need to check ALL 4 directions. If ANY is border-condition, then isBorder = true.
}
// Simpler: Count valid component neighbors.
// int componentNeighbors = 0;
// Check 4 dirs: if in-bounds AND (grid == oldColor) -> componentNeighbors++ (even if visited).
// If componentNeighbors < 4 -> isBorder = true.
// *Need careful check on (grid == oldColor) because grid is static.*
// Re-re-refined Logic:
// Standard DFS traversal.
// Inside DFS(r, c):
// Traverse 4 neighbors.
// If neighbor in-bounds AND same-color AND !visited: DFS(neighbor).
//
// Distinct "Is Border" check:
// Iterate 4 neighbors.
// If neighbor out-of-bounds OR grid[r][c] != oldColor: It's a border.
}
};
// Final Logic below in implementations.
Python
from typing import List
class Solution:
def colorBorder(self, grid: List[List[int]], row: int, col: int, color: int) -> List[List[int]]:
old_color = grid[row][col]
rows, cols = len(grid), len(grid[0])
visited = set()
borders = []
def dfs(r, c):
visited.add((r, c))
is_border = False
for dr, dc in [(-1,0), (1,0), (0,-1), (0,1)]:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols:
if grid[nr][nc] == old_color:
if (nr, nc) not in visited:
dfs(nr, nc)
else:
is_border = True # Neighbor is diff color
else:
is_border = True # Out of bounds
if is_border:
borders.append((r, c))
dfs(row, col)
for r, c in borders:
grid[r][c] = color
return grid
Java
import java.util.ArrayList;
import java.util.List;
class Solution {
public int[][] colorBorder(int[][] grid, int row, int col, int color) {
int oldColor = grid[row][col];
int m = grid.length;
int n = grid[0].length;
boolean[][] visited = new boolean[m][n];
List<int[]> borders = new ArrayList<>();
dfs(grid, row, col, oldColor, visited, borders);
for(int[] p : borders) {
grid[p[0]][p[1]] = color;
}
return grid;
}
private void dfs(int[][] grid, int r, int c, int oldColor,
boolean[][] visited, List<int[]> borders) {
visited[r][c] = true;
boolean isBorder = false;
int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
for(int[] d : dirs) {
int nr = r + d[0];
int nc = c + d[1];
if(nr < 0 || nr >= grid.length || nc < 0 || nc >= grid[0].length) {
isBorder = true;
} else if(grid[nr][nc] != oldColor) {
isBorder = true;
} else if(!visited[nr][nc]) {
dfs(grid, nr, nc, oldColor, visited, borders);
}
}
if(isBorder) {
borders.add(new int[]{r, c});
}
}
}
🌍 Real-World Analogy
Painting a Fence (Outline):
You have a shape filled with color. You want to highlight just the outline (border) with a new color, leaving the inside fill as is. You walk along the edge where the shape meets "empty space" or "different color" and paint it.
🎯 Summary
✅ Separation of Concerns: Traverse first (to identify component), then Color. Do not modify grid while traversing to avoid confusing the "old color" check.
✅ Border Definition: Touching Boundary OR Touching Different Color.
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
vector<vector<int>> colorBorder(vector<vector<int>> &grid, int row, int col,
int color) {
int oldColor = grid[row][col];
int m = grid.size();
int n = grid[0].size();
vector<vector<bool>> visited(m, vector<bool>(n, false));
vector<pair<int, int>> borders;
dfs(grid, row, col, oldColor, visited, borders);
for (auto &p : borders) {
grid[p.first][p.second] = color;
}
return grid;
}
private:
void dfs(vector<vector<int>> &grid, int r, int c, int oldColor,
vector<vector<bool>> &visited, vector<pair<int, int>> &borders) {
visited[r][c] = true;
bool isBorder = false;
int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
for (auto &d : dirs) {
int nr = r + d[0];
int nc = c + d[1];
if (nr < 0 || nr >= grid.size() || nc < 0 || nc >= grid[0].size()) {
isBorder = true;
} else if (grid[nr][nc] != oldColor) {
isBorder = true;
} else if (!visited[nr][nc]) {
dfs(grid, nr, nc, oldColor, visited, borders);
}
}
if (isBorder) {
borders.push_back({r, c});
}
}
};
void printGrid(const vector<vector<int>> &grid) {
cout << "[";
for (size_t i = 0; i < grid.size(); ++i) {
cout << "[";
for (size_t j = 0; j < grid[i].size(); ++j) {
cout << grid[i][j] << (j < grid[i].size() - 1 ? "," : "");
}
cout << "]" << (i < grid.size() - 1 ? "," : "");
}
cout << "]" << endl;
}
int main() {
Solution sol;
vector<vector<int>> grid1 = {{1, 1}, {1, 2}};
cout << "Original 1: ";
printGrid(grid1);
vector<vector<int>> res1 = sol.colorBorder(grid1, 0, 0, 3);
cout << "Colored 1: ";
printGrid(res1); // Expect [[3,3],[3,2]]
vector<vector<int>> grid2 = {{1, 2, 2}, {2, 3, 2}};
cout << "Original 2: ";
printGrid(grid2);
vector<vector<int>> res2 = sol.colorBorder(grid2, 0, 1, 3);
cout << "Colored 2: ";
printGrid(res2); // Expect [[1,3,3],[2,3,3]] ... wait.
// grid2: 0,1 is '2'. It's top.
// component: (0,1), (0,2), (1,2) [all '2']. (1,0) is '2' but disconnected? No
// (0,0)=1. (0,1) neighbors: (0,0)=1 (diff), (0,2)=2 (same), (1,1)=3 (diff).
// Border=Yes. (0,2) neighbors: (0,1)=2 (same), (1,2)=2 (same). Out of bounds
// Right. Border=Yes. (1,2) neighbors: (0,2)=2 (same), (1,1)=3 (diff), (1,0)=2
// (Wait, (1,0) is 2. Is it connected to (1,2)? (1,1)=3 breaks it. So (1,0)
// not connected to (1,2). (1,2) out of bounds Bottom, Right. Border=Yes. So
// all 3 cells are borders. Color=3. Result: [[1,3,3],[2,3,3]].
return 0;
}