力扣-661. 图片平滑器
1.题目
题目地址(661. 图片平滑器 - 力扣(LeetCode))
https://leetcode.cn/problems/image-smoother/
题目描述
图像平滑器 是大小为 3 x 3
的过滤器,用于对图像的每个单元格平滑处理,平滑处理后单元格的值为该单元格的平均灰度。
每个单元格的 平均灰度 定义为:该单元格自身及其周围的 8 个单元格的平均值,结果需向下取整。(即,需要计算蓝色平滑器中 9 个单元格的平均值)。
如果一个单元格周围存在单元格缺失的情况,则计算平均灰度时不考虑缺失的单元格(即,需要计算红色平滑器中 4 个单元格的平均值)。
给你一个表示图像灰度的 m x n
整数矩阵 img
,返回对图像的每个单元格平滑处理后的图像 。
示例 1:
输入:img = [[1,1,1],[1,0,1],[1,1,1]] 输出:[[0, 0, 0],[0, 0, 0], [0, 0, 0]] 解释: 对于点 (0,0), (0,2), (2,0), (2,2): 平均(3/4) = 平均(0.75) = 0 对于点 (0,1), (1,0), (1,2), (2,1): 平均(5/6) = 平均(0.83333333) = 0 对于点 (1,1): 平均(8/9) = 平均(0.88888889) = 0
示例 2:
输入: img = [[100,200,100],[200,50,200],[100,200,100]] 输出: [[137,141,137],[141,138,141],[137,141,137]] 解释: 对于点 (0,0), (0,2), (2,0), (2,2): floor((100+200+200+50)/4) = floor(137.5) = 137 对于点 (0,1), (1,0), (1,2), (2,1): floor((200+200+50+200+100+100)/6) = floor(141.666667) = 141 对于点 (1,1): floor((50+200+200+200+200+100+100+100+100)/9) = floor(138.888889) = 138
提示:
m == img.length
n == img[i].length
1 <= m, n <= 200
0 <= img[i][j] <= 255
2.题解
2.1 朴素解法(模拟)
思路
使用图论中常用的位置数组dirs分别表示八连通方向, 模拟题意, 计算出每个平均值即可
代码
- 语言支持:C++
C++ Code:
class Solution {
public:
vector<vector<int>> imageSmoother(vector<vector<int>>& img) {
int m = img.size(), n = img[0].size();
vector<vector<int>> ans(m, vector<int>(n));
vector<pair<int, int>> dirs{{0, 0}, {0, 1}, {0, -1}, {1, 0}, {1, -1},
{1, 1}, {-1, -1}, {-1, 0}, {-1, 1}};
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int count = 0, tol = 0;
for (auto dir : dirs) {
int x = i + dir.first;
int y = j + dir.second;
if (x < 0 || x >= m || y < 0 || y >= n)
continue;
tol += img[x][y];
count++;
}
ans[i][j] = tol / count;
}
}
return ans;
}
};
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(n)\)
2.2 前缀和(二维)
思路
对于 sum[i][j],它表示从原始图像左上角到 (i, j) 这个位置的所有像素的累计和.
计算方法是利用二维前缀和的思想,通过递推关系 sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + img[i-1][j-1] 来更新 sum 数组。
(注意!!! 这里本来应该是加 img[i][j] 的, 但是注意我们这里的i,j都是从1->m, 1->n, 并不是下标(表示的就是m行n列), 但是在img中存储是按下标存储的!!! 所以这里是img[i-1][j-1])
要注意前缀和数组下标是从1开始的, 而我们统计的img数组下标是从0开始的, 注意后面的统一!!!
讨论边界的时候,注意哪些是要的,哪些是不要的; 比如像这里 c+1, d+1, a+1, b+1都是要的, 所以我们要加上 c+1, d+1, 减去 a, b
代码
class Solution {
public:
vector<vector<int>> imageSmoother(vector<vector<int>>& img) {
int m = img.size(), n = img[0].size();
vector<vector<int>> sum(m + 1, vector<int>(n + 1)); // 前缀和数组(从1开始就不用单独讨论初始化中的i-1了, 因为从1开始,大小多一个!!!)
vector<vector<int>> ans(m, vector<int>(n)); // 结果数组
// 更新二维前缀和数组
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + img[i - 1][j - 1];
}
}
// 计算每个格子实际平均值(注意这里的i, j 是从 0 开始, 而前缀和均是从 1 开始, 所以下面计算tot记得 +1)
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 计算边界
int a = max(0, i - 1), b = max(0, j - 1); // 左边界和上边界
int c = min(m - 1, i + 1), d = min(n - 1, j + 1); // 右边界和下边界
int cnt = (c - a + 1) * (d - b + 1); // 计算总数量, 种树问题记得 + 1
int tot = sum[c+1][d+1] - sum[a][d+1] - sum[c+1][b] + sum[a][b]; // a, b, c, d 对应到前缀和数组中 下标是 差1的, 但是 c+1,d+1是要包含的, a,b是不要包含的(所以没有+1)
ans[i][j] = tot / cnt;
}
}
return ans;
}
};