LeetCode #733 Easy

Flood Fill

Flood Fill is LeetCode 733 (Easy). An image is stored as an m × n grid of integers, where each number is the colour of one pixel. You are also given a starting pixel (sr, sc) and a new color.

Repaint the region that contains the starting pixel, exactly like the paint-bucket tool in an image editor:

  • The region starts with image[sr][sc]; call its colour the original colour.
  • A pixel joins the region if it has the original colour and touches a region pixel up, down, left or right. Diagonal contact does not count.
  • Every pixel in the region becomes color. Every other pixel keeps its value.

Return the modified image. The grid is at most 50 × 50, so a single visit per pixel is all the work there is.

Constraints
  • m == image.length
  • n == image[i].length
  • 1 <= m, n <= 50
  • 0 <= image[i][j], color < 2¹⁶
  • 0 <= sr < m
  • 0 <= sc < n
dfsbfsmatrix
Open on LeetCode ↗
02

Intuition

The pixels with the original colour that touch each other 4-directionally form a connected component of a grid graph: every pixel is a vertex and every up/down/left/right pair of equal pixels is an edge. Flood fill is just "visit every vertex in the component of (sr, sc)", which any graph traversal does.

The neat part is that the repainting is the visited marker. Once a pixel has the new colour it no longer equals the original colour, so the neighbour check rejects it and it is never visited twice. No separate visited array is needed, as long as the new colour really differs from the old one.

How to spot this pattern

The flood fill LeetCode problem is the template for a whole family. Grid questions that ask you to spread from one cell to every connected cell with the same value are the flood fill algorithm: Number of Islands counts components with it, Max Area of Island measures them, Surrounded Regions and Rotting Oranges spread a mark through them. Whenever a problem says "4-directionally connected", think flood fill.

03

Approach

Try it first

Before reading on: what happens to your code if color == image[sr][sc]? Trace it on a 1 × 2 grid of the same colour. Then write the fill without a visited set.

1

Remember the original colour

Read old = image[sr][sc] before changing anything. If old == color, return the image at once: the answer is the image itself, and the paint-as-visited trick would never terminate.

2

Paint the start and put it on a stack

Set image[sr][sc] = color and push (sr, sc). Painting at push time, not pop time, is what guarantees each pixel enters the stack only once.

3

Pop a pixel and check its four neighbours

For each of (r+1, c), (r-1, c), (r, c+1), (r, c-1):

  • outside the grid: skip;
  • colour is not old (a different colour, or already painted): skip;
  • otherwise: paint it and push it.
4

Stop when the stack is empty

Return image. A pixel is painted only when it has the original colour and touches a painted pixel, so nothing outside the region changes, and the search follows every chain of equal neighbours from the start, so no region pixel is missed.

04

Flood Fill solution in Python | C++ | Java

▶1class Solution:
▶2 def floodFill(
▶3 self, image: List[List[int]], sr: int, sc: int, color: int
▶4 ) -> List[List[int]]:
▶5 old = image[sr][sc]
▶6 if old == color:
▶7 return image
▶8 rows, cols = len(image), len(image[0])
▶9 image[sr][sc] = color
▶10 stack = [(sr, sc)]
▶11 while stack:
▶12 r, c = stack.pop()
▶13 for nr, nc in ((r + 1, c), (r - 1, c), (r, c + 1), (r, c - 1)):
▶14 if 0 <= nr < rows and 0 <= nc < cols and image[nr][nc] == old:
▶15 image[nr][nc] = color
▶16 stack.append((nr, nc))
▶17 return image
image0120111112s02101stack(1, 1)paint the start, push it
old1saved before painting
image[1][1]2was 1
stack1pixels waiting
Save the original colour 1 first, then paint the start pixel 2 and push it. From now on a painted pixel no longer equals 1, so the neighbour test will skip it: the paint doubles as the visited mark.
image0120121122s02101stack(0, 1)(1, 0)paint 2, push 2
pop(1, 1)expand this pixel
painted3 pixelsso far
stack2pixels waiting
Pop (1, 1) and look at its side neighbours. (0, 1), (1, 0) still have colour 1, so they are painted now and pushed; painting before pushing means no other pixel can push them again. Rejected: (2, 1) is colour 0; (1, 2) is colour 0.
image0120221122s02201stack(0, 1)(2, 0)(0, 0)paint 2, push 2
pop(1, 0)expand this pixel
painted5 pixelsso far
stack3pixels waiting
Pop (1, 0) and look at its side neighbours (1 falls outside the grid). (2, 0), (0, 0) still have colour 1, so they are painted now and pushed; painting before pushing means no other pixel can push them again. Rejected: (1, 1) is already painted.
image0120221122s02201stack(0, 1)(2, 0)no neighbour to add
pop(0, 0)expand this pixel
painted5 pixelsso far
stack2pixels waiting
Pop (0, 0) and look at its side neighbours (2 fall outside the grid). None of them can join the region, so this branch of the fill ends here. Rejected: (1, 0) is already painted; (0, 1) is already painted.
image0120221122s02201stack(0, 1)no neighbour to add
pop(2, 0)expand this pixel
painted5 pixelsso far
stack1pixels waiting
Pop (2, 0) and look at its side neighbours (2 fall outside the grid). None of them can join the region, so this branch of the fill ends here. Rejected: (1, 0) is already painted; (2, 1) is colour 0.
image0120222122s02201stack(0, 2)paint 1, push 1
pop(0, 1)expand this pixel
painted6 pixelsso far
stack1pixels waiting
Pop (0, 1) and look at its side neighbours (1 falls outside the grid). (0, 2) still has colour 1, so it is painted now and pushed; painting before pushing means no other pixel can push it again. Rejected: (1, 1) is already painted; (0, 0) is already painted.
image0120222122s02201stackemptyno neighbour to add
pop(0, 2)expand this pixel
painted6 pixelsso far
stack0pixels waiting
Pop (0, 2) and look at its side neighbours (2 fall outside the grid). None of them can join the region, so this branch of the fill ends here. Rejected: (1, 2) is colour 0; (0, 1) is already painted.
image0120222122s02201stackemptystack empty → return image
painted6 pixelsthe whole region
untouched1colour 1 but not connected
Done. The stack is empty, so every painted pixel has had its neighbours checked and the region cannot grow further. 1 pixel with colour 1 stays unpainted: it touches the region only at a corner, and flood fill moves up, down, left and right, never diagonally.
05

Recursive DFS flood fill

Each call checks one pixel. If it is inside the grid and still has the original colour, it is painted and the call spreads to its four neighbours; painted pixels stop the recursion.

▶1class Solution:
▶2 def floodFill(
▶3 self, image: List[List[int]], sr: int, sc: int, color: int
▶4 ) -> List[List[int]]:
▶5 old = image[sr][sc]
▶6 if old == color:
▶7 return image
▶8 rows, cols = len(image), len(image[0])
▶9 
▶10 def fill(r: int, c: int) -> None:
▶11 if not (0 <= r < rows and 0 <= c < cols) or image[r][c] != old:
▶12 return
▶13 image[r][c] = color
▶14 fill(r + 1, c)
▶15 fill(r - 1, c)
▶16 fill(r, c + 1)
▶17 fill(r, c - 1)
▶18 
▶19 fill(sr, sc)
▶20 return image
06

Common pitfalls

No early return when the colour is unchanged

✗ Wrong
old = image[sr][sc]
image[sr][sc] = color
stack = [(sr, sc)]
✓ Right
old = image[sr][sc]
if old == color:
    return image

Painting is the only visited marker. With color == old a painted pixel still equals old, so neighbours push each other back and forth forever: an infinite loop iteratively, a stack overflow recursively.

Reading the colour after the first paint

✗ Wrong
image[sr][sc] = color
old = image[sr][sc]
✓ Right
old = image[sr][sc]
image[sr][sc] = color

old now equals color, so no neighbour matches and only the start pixel changes. Save the original colour before touching the grid.

Deep recursion in Python

✗ Wrong
def fill(r, c):
    ...
    fill(r + 1, c)
✓ Right
stack = [(sr, sc)]
while stack:
    r, c = stack.pop()

A snake-shaped region in a 50 × 50 grid is a path of up to 2,500 pixels, and a recursive flood fill Python solution goes one frame deeper per pixel. That passes CPython's default limit of 1,000, so it crashes anywhere the limit is not raised. An explicit stack has no such limit.

Checking the colour before the bounds

✗ Wrong
if image[nr][nc] == old and 0 <= nr < rows and 0 <= nc < cols:
✓ Right
if 0 <= nr < rows and 0 <= nc < cols and image[nr][nc] == old:

The colour read runs first, so nr == rows raises an index error. Worse, in Python nr == -1 silently reads the last row and can paint pixels on the far side of the image. Test the bounds first and let and short-circuit.

07

Complexity

Time
O(m · n)
Space
O(m · n)
Each pixel is painted and pushed at most once and checks four neighbours. The stack (or the recursion depth in a DFS) can hold up to every pixel when the whole image is one region.
08

Flood Fill FAQ

What is the flood fill algorithm?

Flood fill starts at one cell and spreads to every cell connected to it that has the same value, changing each one as it goes. It is a depth-first or breadth-first search on the grid, where neighbouring cells with equal values are joined by edges. It powers the paint-bucket tool, region selection in image editors and the island-counting family of interview problems.

Should flood fill use DFS or BFS?

Either. Both visit exactly the region and cost O(m · n). Swap the stack for a queue (deque.popleft()) and the code above becomes BFS, which fills in rings around the start; DFS snakes along one direction first. The final image is identical.