题目描述

:::fold-green 相关原题

LeetCode 原题: ✅ 661. 图片平滑器

LeetCode 原题对每个位置的有效 3 × 3 邻域求均值并向下取整,边界只统计有效元素;本文使用指定权重进行互相关计算,不取整,边界选择不填充或补零。

:::

给你一个实数矩阵 image、一个 3 × 3 权重矩阵 kernel,以及边界填充参数 padding。

请以步长 1 滑动卷积核,将窗口内各元素与对应权重的乘积相加,返回结果矩阵。采用互相关约定,不翻转卷积核,结果不取整。

  • padding = 0 时不填充边界,输出大小为 (m - 2) × (n - 2)。
  • padding = 1 时在图像四周填充一圈 0,输出大小为 m × n。

其中 m、n 为图像的行数和列数。kernel 由调用者提供;用于平滑滤波时,可以传入总和为 1 的非负权重。

示例 1:

输入: image = [[1,2,3],[4,5,6],[7,8,9]], kernel 全部为 1/9, padding = 0
输出: [[5.0]]
解释: 九个元素的均值为 5,不取整。

示例 2:

输入: image = [[1,2,3],[4,5,6],[7,8,9]], kernel = [[0,0,0],[0,1,0],[0,0,0]], padding = 1
输出: [[1,2,3],[4,5,6],[7,8,9]]
解释: 中心权重为 1,其余为 0,输出保持不变。

提示:

  • 采用神经网络中常见的互相关约定,不翻转卷积核。
  • padding=0 不补边,输出 (m-2)×(n-2)。
  • padding=1 四周补一圈 0,输出 m×n。
  • m,n≥3。
  • 输入均为矩形。
  • 数值有限且计算结果不溢出 double。
  • 边界模式作为参数明确给出。

题意分析

每个输出格子的值都是一个固定窗口内的加权和,窗口之间不依赖,所以直接逐格计算即可。需要先确定窗口能放在哪些位置,再把核内坐标换算回输入坐标。

本题不翻转卷积核,也不对结果取整。补边位置按 0 计算,不需要真的创建一份补零后的矩阵。

解法:按输出位置枚举九个乘积

核心思路

[!blue]

补边后可视为高度 m + 2 * padding、宽度 n + 2 * padding 的矩阵。边长为 3、步长为 1 的窗口可放置 m + 2 * padding - 2 行、n + 2 * padding - 2 列,这就是输出尺寸。

输出位置 (i,j) 对应窗口左上角;核位置 (u,v) 映射回原图的位置为 (i + u - padding, j + v - padding)。坐标有效时累加 image[r][c] * kernel[u][v],越界时其贡献为 0,直接跳过。

九个位置全部处理后,累加值恰好是题面定义的加权和。始终从原图读取并写入独立结果,避免先计算的格子影响后续窗口。

解题步骤

  1. 按输入尺寸和 padding 计算输出尺寸。
  2. 每个输出位置枚举九个核位置,换算对应输入坐标。
  3. 有效输入坐标贡献像素乘权重,越界按 0 处理。

代码实现

class Solution {
    public double[][] filter(double[][] image, double[][] kernel, int padding) {
        int m = image.length;
        int n = image[0].length;
        double[][] out = new double[m + 2 * padding - 2][n + 2 * padding - 2];

        for (int i = 0; i < out.length; i++) {
            for (int j = 0; j < out[0].length; j++) {
                for (int u = 0; u < 3; u++) {
                    for (int v = 0; v < 3; v++) {
                        int r = i + u - padding;
                        int c = j + v - padding;

                        if (r >= 0 && r < m && c >= 0 && c < n) {
                            out[i][j] += image[r][c] * kernel[u][v];
                        }
                    }
                }
            }
        }

        return out;
    }
}
func filter(image, kernel [][]float64, padding int) [][]float64 {
    m, n := len(image), len(image[0])
    out := make([][]float64, m+2*padding-2)
    for i := range out {
        out[i] = make([]float64, n+2*padding-2)
        for j := range out[i] {
            for u := 0; u < 3; u++ {
                for v := 0; v < 3; v++ {
                    r, c := i+u-padding, j+v-padding
                    if r >= 0 && r < m && c >= 0 && c < n {
                        out[i][j] += image[r][c] * kernel[u][v]
                    }
                }
            }
        }
    }
    return out
}

复杂度分析

  • 时间复杂度:$O(mn)$,每个输出位置计算固定的 9 个乘积。
  • 空间复杂度:辅助空间 $O(1)$;另需 $O(mn)$ 保存结果。

关键点总结

[!green]

这是固定核大小的局部计算,输入只读;若题目要求数学卷积,要先把核旋转 180 度。

易错点总结

[!yellow]

  • 坐标换算必须减去 padding,否则补边模式下窗口会偏移。
  • 越界位置贡献为 0,不需要重新归一化剩余权重。
  • 本题采用互相关约定,不翻转核、不向下取整;不要直接套用图片平滑器的平均值规则。

相似题目

题目 难度 关联与区别
661. 图片平滑器 简单 同样枚举邻域,本题使用带权 3×3 核及补边规则,原题计算有效邻域的均值。
补充题 112. 可变窗口的图像均值滤波 中等 均匀权重窗口可用前缀和加速,任意九个权重一般需要逐项乘加。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/2746010454
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!