Grind 75 Question 9: Flood Fill

This is where sauce gets real.

The Problem

An image is represented by an m x n integer grid image where image[i][j] represents the pixel value of the image.

You are also given three integers srsc, and color. You should perform a flood fill on the image starting from the pixel image[sr][sc].

To perform a flood fill, consider the starting pixel, plus any pixels connected 4-directionally to the starting pixel of the same color as the starting pixel, plus any pixels connected 4-directionally to those pixels (also with the same color), and so on. Replace the color of all of the aforementioned pixels with color.

ReturnĀ the modified image after performing the flood fill.

The First Attempts

Reading over this had me like:

Then it had me like:

Then I sat down and truly tried to understand what was going on and spent a half day pondering how exactly to approach the problem.

I’m sure I’m going to have to read all about graphs to fully solve it, but I decided to do things My Way. Here’s the initial (probably unnecessarily long and not working) code:

/**
 * @param {number[][]} image
 * @param {number} sr
 * @param {number} sc
 * @param {number} color
 * @return {number[][]}
 */
var floodFill = function(image, sr, sc, color) {
	image[sr][sc] = color;
	if(image[sr - 1][sc] > 1) {
		image[sr - 1][sc] = color;
	};

	if(image[sr + 1][sc] > 1) {
		image[sr + 1][sc] = color;
	}

	if(image[sr][sc -1] > 1) {
		image[sr ][sc - 1] = color;
	}

	if(image[sr][sc + 1] > 1) {
		image[sr - 1][sc + 1] = color;
	};

for(i = 0; i < image.length; i++) {
	for(j = 0; j < image.length; j++){
		if(image[i][j] > 1 && image[i - 1][j] === color) {
			image[i][j] = color;
			}
		if(image[i][j] > 1 && image[i + 1][j] === color) {
			image[i][j] = color;
			}
		if(image[i][j] > 1 && image[i][j - 1] === color) {
			image[i][j] = color;
			}
		if(image[i][j] > 1 && image[i][j + 1] === color) {
			image[i][j] = color;
			}
		}
	}
	return image;
};

Time to plug it in and let it work its *~*magic*~*.

Of course, the magic doesn’t work. I look at it some more. It should be working — except that I’m searching for some negative index values. So I fix it up, and, hey-o! It works!

/**
 * @param {number[][]} image
 * @param {number} sr
 * @param {number} sc
 * @param {number} color
 * @return {number[][]}
 */
var floodFill = function(image, sr, sc, color) {
	image[sr][sc] = color;
    
	if(sr > 0 && image[sr - 1][sc] > 0) {
		image[sr - 1][sc] = color;
	};

	if(sr < image.length - 2 && image[sr + 1][sc] > 0) {
		image[sr + 1][sc] = color;
	}

	if(sc > 0&& image[sr][sc -1] > 0) {
		image[sr ][sc - 1] = color;
	}

	if(sc < image.length - 2 && image[sr][sc + 1] > 1) {
		image[sr - 1][sc + 1] = color;
	};

for(i = 0; i < image.length; i++) {
	for(j = 0; j < image.length; j++){
		if(i > 0 && image[i][j] > 0 && image[i - 1][j] === color) {
			image[i][j] = color;
			}
		if(i < image.length -2 && image[i][j] > 0 && image[i + 1][j] === color) {
			image[i][j] = color;
			}
		if(j > 0 && image[i][j] > 0 && image[i][j - 1] === color) {
			image[i][j] = color;
			}
		if(j < image.length - 2 && image[i][j] > 0 && image[i][j + 1] === color) {
			image[i][j] = color;
			}
		}
	}
	return image;
};

I am 100% positive that it won’t work if I submit it, though. Upon testing…runtime error! Fix it some more: wrong answer.

Time to visit my old pal Neetcode and learn about graphs.

The Solution

Turns out Neetcode didn’t cover this one! So I watched this guy’s video, which is a decent explanation. My approach wasn’t wrong, exactly, but it wasn’t right, either. The missing key was, once again, a recursive function. Gah! Here’s the correct code:

/**
 * @param {number[][]} image
 * @param {number} sr
 * @param {number} sc
 * @param {number} color
 * @return {number[][]}
 */
var floodFill = function(image, sr, sc, color) {
    if(image[sr][sc] === color){
        return image;
    }
    fillPixels(image, sr, sc, image[sr][sc], color);
    return image;
    
    function fillPixels(image, sr, sc, color, newColor) {
        if(sr < 0 || sc < 0 || sr >= image.length || sc >= image[0].length || image[sr][sc] != color){
           return;
           }
        image[sr][sc] = newColor;
        fillPixels(image, sr+1, sc, color, newColor);
        fillPixels(image, sr, sc+1, color, newColor);
        fillPixels(image, sr-1, sc, color, newColor);
        fillPixels(image, sr, sc-1, color, newColor);
    };
};

To explain it as best I can:

  1. If the pixel color matches the new color, just return the entire image. It’s dumb, but I believe this is called an “edge case”. I got a wrong submitted answer on Leetcode without it.
  2. Barring that, call the fillPixels function on the target pixel, and when it’s done, return the completed image.
  3. And what does fillPixels do in the background? Well, it takes a look at the pixel.
    1. If it’s out of bounds in any way, or if it doesn’t match the original color of the pixel being modified, move on without changing anything.
    2. If it isn’t out of bounds and matches the original color, change it to the new color.
  4. And because it’s a recursive function, we do that to every square touching the colored pixel. And again — somehow it knows when to stop executing. When every touched pixel has been changed to the new color. The end. Question done.

What I Learned

  1. More about recursive functions — namely, that I need to be more on the lookout for them!
  2. Depth-first searches. Or, I will learn about them. As soon as I’m done with this post and get a free moment.
  3. Learned all about…uh I forget what they’re called. Maybe this counts as a graph. 2-D array? Unsure. But I discovered on my own that array[0].length gives me the length of an individual row. Cool!
5

mins
read

Begin typing your search above and press return to search. Press Esc to cancel.