A clear explanation of averaging neighboring pixels in a matrix using direct simulation.
Problem Restatement
We are given a grayscale image represented as a 2D integer matrix img.
Each cell contains a pixel value.
We must create a smoothed image.
For every cell, compute the average of:
- The cell itself
- All valid neighboring cells
Neighbors include all eight surrounding directions:
top
bottom
left
right
top-left
top-right
bottom-left
bottom-rightThe average is rounded down to the nearest integer.
Cells outside the image boundaries are ignored.
Return the resulting smoothed matrix.
Input and Output
| Item | Meaning |
|---|---|
| Input | A 2D integer matrix img |
| Output | A smoothed 2D integer matrix |
| Neighbor rule | Include all valid adjacent cells and the cell itself |
| Averaging rule | Use floor division |
Example function shape:
def imageSmoother(img: list[list[int]]) -> list[list[int]]:
...Examples
Consider:
img = [
[1, 1, 1],
[1, 0, 1],
[1, 1, 1],
]For the center cell:
0we average all 9 cells:
1 + 1 + 1
1 + 0 + 1
1 + 1 + 1The sum is:
8The count is:
9So:
8 // 9 = 0For the top-left corner:
1only 4 cells are valid:
1 1
1 0The sum is:
3The count is:
4So:
3 // 4 = 0The final output is:
[
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
]Another example:
img = [
[100, 200, 100],
[200, 50, 200],
[100, 200, 100],
]The smoothed image becomes:
[
[137, 141, 137],
[141, 138, 141],
[137, 141, 137],
]First Thought: Direct Simulation
For every cell, we can check all neighboring positions.
There are only:
9 possible positionsincluding the cell itself.
So for each cell:
- Visit all neighboring offsets.
- Ignore positions outside the matrix.
- Compute the total sum.
- Count how many cells were included.
- Store:
sum // countThis is efficient because each cell only checks a constant number of neighbors.
Key Insight
The important detail is boundary handling.
For a middle cell, all 9 surrounding positions exist.
For an edge or corner cell, some neighbors fall outside the matrix and must be ignored.
So before using a neighbor position:
(nr, nc)we must verify:
0 <= nr < rows
0 <= nc < colsDirection Offsets
We can represent all neighboring positions using row and column offsets.
The offsets are:
| Row offset | Column offset |
|---|---|
-1 | -1 |
-1 | 0 |
-1 | 1 |
0 | -1 |
0 | 0 |
0 | 1 |
1 | -1 |
1 | 0 |
1 | 1 |
For a cell:
(r, c)the neighbor becomes:
(r + dr, c + dc)Algorithm
- Get matrix dimensions.
- Create an output matrix.
- For every cell:
- Initialize
total = 0 - Initialize
count = 0
- Initialize
- Check all 9 neighboring offsets.
- If the neighbor is inside bounds:
- Add its value to
total - Increase
count
- Add its value to
- Store:
total // countin the result matrix.
- Return the result.
Correctness
For each cell (r, c), the algorithm examines every possible neighboring position, including the cell itself.
A neighboring position is included only if it lies inside the matrix boundaries. Therefore, the algorithm includes exactly the valid neighbors required by the problem.
The variables total and count accumulate:
| Variable | Meaning |
|---|---|
total | Sum of all valid neighboring pixel values |
count | Number of valid neighboring cells |
The algorithm stores:
total // countwhich is exactly the floor of the average required by the problem statement.
Since this process is applied independently to every cell, the resulting matrix is the correctly smoothed image.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(rows * cols) | Each cell checks only 9 neighbors |
| Space | O(rows * cols) | The output matrix is stored separately |
The neighbor loop is constant size, so it does not affect asymptotic complexity.
Implementation
from typing import List
class Solution:
def imageSmoother(self, img: List[List[int]]) -> List[List[int]]:
rows = len(img)
cols = len(img[0])
result = [[0] * cols for _ in range(rows)]
for r in range(rows):
for c in range(cols):
total = 0
count = 0
for dr in (-1, 0, 1):
for dc in (-1, 0, 1):
nr = r + dr
nc = c + dc
if 0 <= nr < rows and 0 <= nc < cols:
total += img[nr][nc]
count += 1
result[r][c] = total // count
return resultCode Explanation
We first compute the matrix dimensions:
rows = len(img)
cols = len(img[0])Then create the output matrix:
result = [[0] * cols for _ in range(rows)]We process every cell:
for r in range(rows):
for c in range(cols):For each cell, we track:
total = 0
count = 0Then we check all neighboring offsets:
for dr in (-1, 0, 1):
for dc in (-1, 0, 1):The neighbor position becomes:
nr = r + dr
nc = c + dcWe only use neighbors inside the image:
if 0 <= nr < rows and 0 <= nc < cols:Then we update the running sum and count:
total += img[nr][nc]
count += 1Finally, we compute the floor average:
result[r][c] = total // countAfter all cells are processed, we return the smoothed image.
Testing
def run_tests():
s = Solution()
img = [
[1, 1, 1],
[1, 0, 1],
[1, 1, 1],
]
assert s.imageSmoother(img) == [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
]
img = [
[100, 200, 100],
[200, 50, 200],
[100, 200, 100],
]
assert s.imageSmoother(img) == [
[137, 141, 137],
[141, 138, 141],
[137, 141, 137],
]
img = [
[5],
]
assert s.imageSmoother(img) == [
[5],
]
img = [
[1, 2],
[3, 4],
]
assert s.imageSmoother(img) == [
[2, 2],
[2, 2],
]
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
3×3 matrix with center 0 | Standard example |
| Larger values | Confirms averaging correctness |
| Single-cell image | Smallest valid input |
| 2×2 matrix | Checks corner handling and boundaries |