What is the Flood Fill Algorithm?
Flood Fill is an algorithm that determines the area connected to a given node in a multi-dimensional array (matrix). It is most famously used in the “bucket fill” tool of paint programs to fill connected, similarly-colored areas with a different color. It operates by exploring adjacent spaces (using BFS or DFS) until the boundary of the region is reached.
Explanation
- The Core Problem: Given a 2D grid where each cell represents a color/state, starting at a specific coordinate, we want to replace its color and all adjacent cells of the same color with a new replacement color.
- The Solution: We can use basic graph traversal algorithms. A 2D grid is just a graph where every pixel is a node, and it has edges connecting to its 4-way (Up, Down, Left, Right) or 8-way neighbors.
DFS vs BFS for Flood Fill
- DFS (Depth-First Search): Explores a path as deeply as possible before backtracking. Easy to write using recursion, but can cause
StackOverflowon very large grids (like 1000x1000 images). - BFS (Breadth-First Search): Explores outward in a growing ring (like a ripple in water). Requires an explicit Queue, but prevents stack overflows and is generally safer for large matrices.
How It Works
Step-by-Step Execution (Recursive DFS)
-
- Check Base Cases:
- If the starting coordinates are out of bounds, return.
- If the color of the cell is NOT the original target color, return.
- If the color of the cell is ALREADY the replacement color, return (prevents infinite loops).
-
- Update Color: Change the color of cell to the new color.
-
- Recurse: Make recursive calls to the adjacent cells:
FloodFill(X + 1, Y)(Right)FloodFill(X - 1, Y)(Left)FloodFill(X, Y + 1)(Up)FloodFill(X, Y - 1)(Down)
flowchart TD A["Start FloodFill at (X, Y)"] --> B{"Out of Bounds?"} B -- Yes --> C["Return"] B -- No --> D{"Is color == targetColor?"} D -- No --> C D -- Yes --> E["Set color(X, Y) = newColor"] E --> F["FloodFill(X+1, Y)\nFloodFill(X-1, Y)\nFloodFill(X, Y+1)\nFloodFill(X, Y-1)"] F --> G["Region Filled!"]
Complexity Analysis
| Step | Time Complexity | Space Complexity |
|---|---|---|
| Iterating/Recursion | (Stack or Queue size) | |
| Total (Average/Worst) | O(N) | O(N) |
- Where is the number of pixels/cells matching the target color.
Implementation
-
Flood Fill Implementations
Below are implementations utilizing the recursive DFS strategy, which is the most standard and elegant way to write Flood Fill.
def flood_fill(image, sr, sc, new_color):
rows = len(image)
cols = len(image[0])
target_color = image[sr][sc]
if target_color == new_color:
return image
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols:
return
if image[r][c] != target_color:
return
image[r][c] = new_color
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
dfs(sr, sc)
return image
if __name__ == "__main__":
img = [
[1, 1, 1, 2],
[1, 1, 0, 2],
[1, 0, 1, 2],
[2, 2, 2, 2]
]
print("Original:")
for row in img: print(row)
flood_fill(img, 1, 1, 3)
print("\nFilled:")
for row in img: print(row)#include <iostream>
#include <vector>
using namespace std;
void dfs(vector<vector<int>>& image, int r, int c, int target_color, int new_color) {
if (r < 0 || r >= image.size() || c < 0 || c >= image[0].size() || image[r][c] != target_color) {
return;
}
image[r][c] = new_color;
dfs(image, r + 1, c, target_color, new_color);
dfs(image, r - 1, c, target_color, new_color);
dfs(image, r, c + 1, target_color, new_color);
dfs(image, r, c - 1, target_color, new_color);
}
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int new_color) {
int target_color = image[sr][sc];
if (target_color != new_color) {
dfs(image, sr, sc, target_color, new_color);
}
return image;
}
int main() {
vector<vector<int>> img = {
{1, 1, 1, 2},
{1, 1, 0, 2},
{1, 0, 1, 2},
{2, 2, 2, 2}
};
floodFill(img, 1, 1, 3);
for (auto row : img) {
for (int val : row) cout << val << " ";
cout << "\n";
}
return 0;
}function floodFill(image, sr, sc, newColor) {
const rows = image.length;
const cols = image[0].length;
const targetColor = image[sr][sc];
if (targetColor === newColor) return image;
function dfs(r, c) {
if (r < 0 || r >= rows || c < 0 || c >= cols || image[r][c] !== targetColor) {
return;
}
image[r][c] = newColor;
dfs(r + 1, c);
dfs(r - 1, c);
dfs(r, c + 1);
dfs(r, c - 1);
}
dfs(sr, sc);
return image;
}
let img = [
[1, 1, 1, 2],
[1, 1, 0, 2],
[1, 0, 1, 2],
[2, 2, 2, 2]
];
floodFill(img, 1, 1, 3);
console.log(img);import java.util.Arrays;
public class FloodFillAlgorithm {
private static void dfs(int[][] image, int r, int c, int targetColor, int newColor) {
if (r < 0 || r >= image.length || c < 0 || c >= image[0].length || image[r][c] != targetColor) {
return;
}
image[r][c] = newColor;
dfs(image, r + 1, c, targetColor, newColor);
dfs(image, r - 1, c, targetColor, newColor);
dfs(image, r, c + 1, targetColor, newColor);
dfs(image, r, c - 1, targetColor, newColor);
}
public static int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
int targetColor = image[sr][sc];
if (targetColor != newColor) {
dfs(image, sr, sc, targetColor, newColor);
}
return image;
}
public static void main(String[] args) {
int[][] img = {
{1, 1, 1, 2},
{1, 1, 0, 2},
{1, 0, 1, 2},
{2, 2, 2, 2}
};
floodFill(img, 1, 1, 3);
for (int[] row : img) {
System.out.println(Arrays.toString(row));
}
}
}#include <stdio.h>
void dfs(int rows, int cols, int image[rows][cols], int r, int c, int target, int newC) {
if (r < 0 || r >= rows || c < 0 || c >= cols || image[r][c] != target) {
return;
}
image[r][c] = newC;
dfs(rows, cols, image, r + 1, c, target, newC);
dfs(rows, cols, image, r - 1, c, target, newC);
dfs(rows, cols, image, r, c + 1, target, newC);
dfs(rows, cols, image, r, c - 1, target, newC);
}
void floodFill(int rows, int cols, int image[rows][cols], int sr, int sc, int newC) {
int target = image[sr][sc];
if (target != newC) {
dfs(rows, cols, image, sr, sc, target, newC);
}
}
int main() {
int R = 4, C = 4;
int img[4][4] = {
{1, 1, 1, 2},
{1, 1, 0, 2},
{1, 0, 1, 2},
{2, 2, 2, 2}
};
floodFill(R, C, img, 1, 1, 3);
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
printf("%d ", img[i][j]);
}
printf("\n");
}
return 0;
}
When to Use Flood Fill
flowchart TD Q{"Need to find/modify a connected\ncomponent in a grid?"} Q -- Yes --> S1{"Is the grid absolutely massive\n(e.g., 4000x4000)?"} S1 -- No --> R1["✅ Use Recursive DFS Flood Fill\n(Simple and clean)"] S1 -- Yes --> R2["✅ Use BFS Flood Fill\n(Prevents StackOverflow)"]
✅ Use Flood Fill When
- Building paint/graphics applications.
- Determining connected components in 2D matrices (e.g., counting islands).
- Implementing games (like Minesweeper blank tile reveals or Go captures).
❌ Avoid Flood Fill When
- Searching for the shortest path between two points. Flood fill blindly colors everything in the region; you should use Lee’s Algorithm (BFS) or A* instead.
Key Takeaways
- Grid as a Graph — standardizes the concept that 2D matrix arrays are just graphs with implied 4-way or 8-way edges.
- Infinite Loop Guard — immediately checking if
target_color == new_colorprevents the algorithm from recursing forever on already-painted cells. - Memory Tradeoff — DFS relies on the call stack (which is severely limited in size), while BFS relies on a heap-allocated Queue structure (much safer for huge matrices).