Facebook Pixel

661. Image Smoother

Problem Description

You are given a 2D matrix img representing a grayscale image where each cell contains an integer value. Your task is to apply an image smoother filter to this image.

An image smoother is a 3×3 filter that works by:

  1. For each cell in the image, consider the cell itself and its 8 surrounding neighbors (forming a 3×3 grid with the current cell at the center)
  2. Calculate the average of all valid cells in this 3×3 grid
  3. Round down this average (using integer division) to get the smoothed value
  4. Replace the original cell value with this smoothed value

Important edge cases:

  • If a cell is at the border or corner of the image, some of its surrounding cells won't exist
  • In such cases, only consider the cells that actually exist when calculating the average
  • For example, a corner cell will only have 4 cells total (itself + 3 neighbors), while an edge cell will have 6 cells total (itself + 5 neighbors)

The function should return a new 2D matrix where each cell contains the smoothed value calculated using the above rules.

Example:

  • For a cell in the middle of the image with value 5, surrounded by values [1, 2, 3, 4, 6, 7, 8, 9], the smoothed value would be (5 + 1 + 2 + 3 + 4 + 6 + 7 + 8 + 9) // 9 = 45 // 9 = 5
  • For a corner cell with value 10, with only 3 neighbors [20, 30, 40], the smoothed value would be (10 + 20 + 30 + 40) // 4 = 100 // 4 = 25
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The problem asks us to smooth each pixel by averaging it with its neighbors. The key insight is that this is essentially a local operation - each cell in the output depends only on a small neighborhood around the corresponding cell in the input.

Since we need to process every cell independently and the smoothing of one cell doesn't affect the smoothing of another cell, we can use a straightforward approach: iterate through each cell and compute its smoothed value directly.

For each cell (i, j), we need to look at all cells in a 3×3 grid centered at (i, j). This means checking cells from (i-1, j-1) to (i+1, j+1). The challenge is handling boundary conditions - when we're at the edges or corners of the image, some of these neighboring positions will be out of bounds.

Rather than writing separate logic for corners, edges, and interior cells, we can use a unified approach: for each of the 9 potential positions in the 3×3 grid, check if the position is valid (within the image bounds). If it is valid, include that cell's value in our sum and increment our count. This way, the code automatically adapts to any position - interior cells will have 9 valid neighbors, edge cells will have 6, and corner cells will have 4.

The final smoothed value is simply the integer division of the sum by the count: sum // count. We need to create a new matrix to store these smoothed values because we can't modify the original image while we're still reading from it (otherwise, we'd be using already-smoothed values when calculating subsequent cells, which would give incorrect results).

Solution Approach

We implement a direct traversal approach to solve this problem. Here's the step-by-step implementation:

  1. Initialize the result matrix: First, we get the dimensions of the input image with m, n = len(img), len(img[0]). Then we create a new 2D matrix ans with the same dimensions, initialized with zeros: ans = [[0] * n for _ in range(m)]. This will store our smoothed values.

  2. Iterate through each cell: We use two nested loops to traverse every cell in the original image:

    for i in range(m):
        for j in range(n):
  3. Process the 3×3 neighborhood: For each cell (i, j), we need to examine all cells in its 3×3 neighborhood. We initialize two variables:

    • s = 0: to accumulate the sum of valid cell values
    • cnt = 0: to count how many valid cells we find
  4. Check surrounding cells: We use another pair of nested loops to check all 9 positions in the 3×3 grid:

    for x in range(i - 1, i + 2):
        for y in range(j - 1, j + 2):

    This gives us positions from (i-1, j-1) to (i+1, j+1), covering the entire 3×3 area.

  5. Validate and accumulate: For each position (x, y), we check if it's within bounds:

    if 0 <= x < m and 0 <= y < n:
        cnt += 1
        s += img[x][y]

    Only valid positions contribute to our sum and count.

  6. Calculate the smoothed value: After checking all 9 potential positions, we compute the average using integer division: ans[i][j] = s // cnt. This automatically rounds down as required.

  7. Return the result: After processing all cells, we return the ans matrix containing all smoothed values.

The time complexity is O(m × n) since we visit each cell once, and for each cell, we check at most 9 neighbors (constant work). The space complexity is also O(m × n) for storing the result matrix.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with a 3×3 matrix to illustrate the solution approach:

Input:

img = [[1, 1, 1],
       [1, 0, 1],
       [1, 1, 1]]

We'll calculate the smoothed value for a few representative cells:

Cell (0, 0) - Corner Cell:

  • Position (0, 0) has value 1
  • Check 3×3 neighborhood centered at (0, 0):
    • (-1, -1): Out of bounds, skip
    • (-1, 0): Out of bounds, skip
    • (-1, 1): Out of bounds, skip
    • (0, -1): Out of bounds, skip
    • (0, 0): Valid, value = 1
    • (0, 1): Valid, value = 1
    • (1, -1): Out of bounds, skip
    • (1, 0): Valid, value = 1
    • (1, 1): Valid, value = 0
  • Sum = 1 + 1 + 1 + 0 = 3
  • Count = 4 valid cells
  • Smoothed value = 3 // 4 = 0

Cell (1, 1) - Center Cell:

  • Position (1, 1) has value 0
  • Check 3×3 neighborhood centered at (1, 1):
    • All 9 positions are valid: (0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)
    • Values: 1, 1, 1, 1, 0, 1, 1, 1, 1
  • Sum = 1 + 1 + 1 + 1 + 0 + 1 + 1 + 1 + 1 = 8
  • Count = 9 valid cells
  • Smoothed value = 8 // 9 = 0

Cell (0, 1) - Edge Cell:

  • Position (0, 1) has value 1
  • Check 3×3 neighborhood centered at (0, 1):
    • (-1, 0), (-1, 1), (-1, 2): Out of bounds
    • (0, 0), (0, 1), (0, 2): Valid, values = 1, 1, 1
    • (1, 0), (1, 1), (1, 2): Valid, values = 1, 0, 1
  • Sum = 1 + 1 + 1 + 1 + 0 + 1 = 5
  • Count = 6 valid cells
  • Smoothed value = 5 // 6 = 0

Final Output:

ans = [[0, 0, 0],
       [0, 0, 0],
       [0, 0, 0]]

The algorithm processes each cell independently, counting only valid neighbors within bounds, and computes the integer average to produce the smoothed image.

Solution Implementation

1class Solution:
2    def imageSmoother(self, img: List[List[int]]) -> List[List[int]]:
3        # Get dimensions of the image
4        rows, cols = len(img), len(img[0])
5      
6        # Initialize result matrix with same dimensions
7        result = [[0] * cols for _ in range(rows)]
8      
9        # Process each cell in the image
10        for row in range(rows):
11            for col in range(cols):
12                # Initialize sum and count for averaging
13                total_sum = 0
14                cell_count = 0
15              
16                # Check all 9 cells in 3x3 grid centered at (row, col)
17                for neighbor_row in range(row - 1, row + 2):
18                    for neighbor_col in range(col - 1, col + 2):
19                        # Only include cells that are within image boundaries
20                        if 0 <= neighbor_row < rows and 0 <= neighbor_col < cols:
21                            cell_count += 1
22                            total_sum += img[neighbor_row][neighbor_col]
23              
24                # Calculate average using integer division (floor)
25                result[row][col] = total_sum // cell_count
26      
27        return result
28
1class Solution {
2    /**
3     * Applies a smoothing filter to an image by replacing each pixel value
4     * with the average of its surrounding cells (including itself).
5     * 
6     * @param img The input 2D array representing the image
7     * @return A new 2D array with smoothed values
8     */
9    public int[][] imageSmoother(int[][] img) {
10        // Get dimensions of the input image
11        int rows = img.length;
12        int cols = img[0].length;
13      
14        // Initialize result array with same dimensions
15        int[][] result = new int[rows][cols];
16      
17        // Iterate through each pixel in the image
18        for (int row = 0; row < rows; row++) {
19            for (int col = 0; col < cols; col++) {
20                // Calculate smoothed value for current pixel
21                int sum = 0;
22                int validCellCount = 0;
23              
24                // Check all 9 cells in the 3x3 grid centered at (row, col)
25                for (int neighborRow = row - 1; neighborRow <= row + 1; neighborRow++) {
26                    for (int neighborCol = col - 1; neighborCol <= col + 1; neighborCol++) {
27                        // Only include cells that are within image boundaries
28                        if (neighborRow >= 0 && neighborRow < rows && 
29                            neighborCol >= 0 && neighborCol < cols) {
30                            validCellCount++;
31                            sum += img[neighborRow][neighborCol];
32                        }
33                    }
34                }
35              
36                // Calculate average and apply floor division
37                result[row][col] = sum / validCellCount;
38            }
39        }
40      
41        return result;
42    }
43}
44
1class Solution {
2public:
3    vector<vector<int>> imageSmoother(vector<vector<int>>& img) {
4        // Get the dimensions of the image
5        int rows = img.size();
6        int cols = img[0].size();
7      
8        // Initialize the result matrix with the same dimensions
9        vector<vector<int>> result(rows, vector<int>(cols));
10      
11        // Iterate through each cell in the image
12        for (int row = 0; row < rows; ++row) {
13            for (int col = 0; col < cols; ++col) {
14                int sum = 0;        // Sum of valid neighboring cells
15                int count = 0;      // Count of valid neighboring cells
16              
17                // Check all 9 cells in the 3x3 grid centered at (row, col)
18                for (int neighborRow = row - 1; neighborRow <= row + 1; ++neighborRow) {
19                    for (int neighborCol = col - 1; neighborCol <= col + 1; ++neighborCol) {
20                        // Skip if the neighbor is out of bounds
21                        if (neighborRow < 0 || neighborRow >= rows || 
22                            neighborCol < 0 || neighborCol >= cols) {
23                            continue;
24                        }
25                      
26                        // Add valid neighbor to sum and increment count
27                        count++;
28                        sum += img[neighborRow][neighborCol];
29                    }
30                }
31              
32                // Calculate the average (floor division) and store in result
33                result[row][col] = sum / count;
34            }
35        }
36      
37        return result;
38    }
39};
40
1/**
2 * Applies a smoothing filter to an image by replacing each pixel value 
3 * with the average of its surrounding 3x3 grid (including itself)
4 * @param img - 2D array representing the image where each element is a pixel value
5 * @returns A new 2D array with smoothed pixel values
6 */
7function imageSmoother(img: number[][]): number[][] {
8    const rowCount: number = img.length;
9    const columnCount: number = img[0].length;
10  
11    // Initialize result matrix with the same dimensions as input
12    const result: number[][] = Array.from(
13        { length: rowCount }, 
14        () => Array(columnCount).fill(0)
15    );
16  
17    // Process each pixel in the image
18    for (let row = 0; row < rowCount; row++) {
19        for (let col = 0; col < columnCount; col++) {
20            let sum: number = 0;
21            let validCellCount: number = 0;
22          
23            // Check all cells in the 3x3 grid centered at (row, col)
24            for (let neighborRow = row - 1; neighborRow <= row + 1; neighborRow++) {
25                for (let neighborCol = col - 1; neighborCol <= col + 1; neighborCol++) {
26                    // Only include cells that are within image boundaries
27                    if (neighborRow >= 0 && neighborRow < rowCount && 
28                        neighborCol >= 0 && neighborCol < columnCount) {
29                        validCellCount++;
30                        sum += img[neighborRow][neighborCol];
31                    }
32                }
33            }
34          
35            // Calculate and store the floor of the average value
36            result[row][col] = Math.floor(sum / validCellCount);
37        }
38    }
39  
40    return result;
41}
42

Time and Space Complexity

The time complexity is O(m × n), where m and n are the number of rows and columns of the input matrix img, respectively. This is because the algorithm iterates through each cell in the matrix exactly once with the outer two loops. For each cell, it examines at most 9 neighboring cells (including itself) in a 3×3 window, which is a constant operation O(1). Therefore, the overall time complexity is O(m × n) × O(1) = O(m × n).

The space complexity is O(m × n) for the output array ans which stores the smoothed image. However, if we exclude the space required for the output array (as it's necessary for returning the result), the additional space complexity is O(1) since the algorithm only uses a constant amount of extra variables (s, cnt, x, y) regardless of the input size.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Modifying the Original Image In-Place

One of the most common mistakes is trying to update the original image directly instead of creating a new result matrix. This leads to incorrect results because as you smooth cells, you're changing values that neighboring cells will later need for their own calculations.

Incorrect approach:

def imageSmoother(self, img: List[List[int]]) -> List[List[int]]:
    rows, cols = len(img), len(img[0])
  
    for row in range(rows):
        for col in range(cols):
            total_sum = 0
            cell_count = 0
          
            for neighbor_row in range(row - 1, row + 2):
                for neighbor_col in range(col - 1, col + 2):
                    if 0 <= neighbor_row < rows and 0 <= neighbor_col < cols:
                        cell_count += 1
                        total_sum += img[neighbor_row][neighbor_col]
          
            # WRONG: This modifies the original values that neighbors need
            img[row][col] = total_sum // cell_count
  
    return img

Why this fails: When processing cell (0,1), it uses the already-modified value of cell (0,0) instead of the original value, leading to cascading errors throughout the image.

Solution: Always create a separate result matrix to store the smoothed values while keeping the original image unchanged during the entire computation.

2. Incorrect Boundary Checking Logic

Another frequent error is using or instead of and in boundary conditions, or getting the comparison operators wrong.

Incorrect boundary check:

# Wrong: Using OR instead of AND
if 0 <= neighbor_row or neighbor_row < rows or 0 <= neighbor_col or neighbor_col < cols:
    # This will include out-of-bounds cells

# Wrong: Incorrect comparison
if neighbor_row >= 0 < rows and neighbor_col >= 0 < cols:
    # This doesn't properly check the upper bounds

Solution: Use proper logical AND operations with clear boundary checks:

if 0 <= neighbor_row < rows and 0 <= neighbor_col < cols:
    # Correctly checks all boundaries

3. Using Float Division Instead of Integer Division

The problem specifically asks to round down the average, which means using integer division.

Incorrect:

result[row][col] = total_sum / cell_count  # Returns float

Solution: Use integer division operator //:

result[row][col] = total_sum // cell_count  # Correctly rounds down
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following uses divide and conquer strategy?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More