661.图片平滑器

目标

图像平滑器 是大小为 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

思路

将图像中任意像素点的灰度值变为其自身以及周围八个像素灰度值的平均值。

可以使用二维前缀和,prefix[i][j] 表示左上顶点为 (0, 0) 右下顶点为 (i, j) 的矩形内的所有元素和。左上顶点为 (p, q) 右下顶点为 (i, j) 的矩形内所有元素和为 prefix[i][j] - prefix[i][q] - prefix[p][j] + prefix[p][q]。注意这里的坐标是顶点的坐标,而题目中的坐标表示格子的坐标,这样定义的前缀和表示以该格子为右下顶点的所有元素和,包括格子所在行列。

使用前缀和时多初始化一行一列可以大大简化代码逻辑,否则我们需要单独初始化第一行,第一列,并且需要在计算二维前缀和时判断下标越界。定义 prefix[i][j] 表示对角线 (0, 0) ~ (i - 1, j - 1) 矩形内的元素和,对于 m x n 矩阵,prefix[m][n] 表示所有元素和。以 (p, q) 为左上顶点,(i, j) 为右下顶点的前缀和为 prefix[i + 1][j + 1] - prefix[i + 1][q] - prefix[p][j + 1] + prefix[p][q]

代码


/**
 * @date 2024-11-18 9:10
 */
public class ImageSmoother661 {

    public int[][] imageSmoother_v1(int[][] img) {
        int m = img.length;
        int n = img[0].length;
        int[][] prefix = new int[m + 1][n + 1];
        for (int r = 1; r <= m; r++) {
            for (int c = 1; c <= n; c++) {
                prefix[r][c] = prefix[r - 1][c] + prefix[r][c - 1] - prefix[r - 1][c - 1] + img[r - 1][c - 1];
            }
        }
        for (int r = 0; r < m; r++) {
            int i = Math.min(m - 1, r + 1);
            int p = Math.max(0, r - 1);
            for (int c = 0; c < n; c++) {
                int j = Math.min(n - 1, c + 1);
                int q = Math.max(0, c - 1);
                int cnt = (i - p + 1) * (j - q + 1);
                img[r][c] = (prefix[i + 1][j + 1] - prefix[p][j + 1] - prefix[i + 1][q] + prefix[p][q]) / cnt;
            }
        }
        return img;
    }

}

性能

3148.矩阵中的最大得分

目标

给你一个由 正整数 组成、大小为 m x n 的矩阵 grid。你可以从矩阵中的任一单元格移动到另一个位于正下方或正右侧的任意单元格(不必相邻)。从值为 c1 的单元格移动到值为 c2 的单元格的得分为 c2 - c1 。

你可以从 任一 单元格开始,并且必须至少移动一次。

返回你能得到的 最大 总得分。

示例 1:

输入:grid = [[9,5,7,3],[8,9,6,1],[6,7,14,3],[2,5,3,1]]
输出:9
解释:从单元格 (0, 1) 开始,并执行以下移动:
- 从单元格 (0, 1) 移动到 (2, 1),得分为 7 - 5 = 2 。
- 从单元格 (2, 1) 移动到 (2, 2),得分为 14 - 7 = 7 。
总得分为 2 + 7 = 9 。

示例 2:

输入:grid = [[4,3,2],[3,2,1]]
输出:-1
解释:从单元格 (0, 0) 开始,执行一次移动:从 (0, 0) 到 (0, 1) 。得分为 3 - 4 = -1 。

说明:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 1000
  • 4 <= m * n <= 10^5
  • 1 <= grid[i][j] <= 10^5

思路

有一个二维矩阵 grid,可以从任意格子出发向下或右移动(不必相邻),移动的得分为元素值之差 to - from,求最大的得分数。

首先想到动态规划,关键点是想清楚最大得分其实就是以当前元素为左上顶点(不包括其自身),以 m - 1, n - 1 为右下顶点的矩形中的最大值减去当前元素值。如果把状态定义错误还是会超时的,比如当前元素出发所能取得的最大值,需要依次向下/向右比较,时间复杂度 O(m * n * (m +n))m = 1000 n = 95,循环次数 10^5 * (10^3 + 95) 超时,耗时878 ms,下午又提交了几次,耗时 1200 ~ 1300 ms。参考特殊数组II 中的时间复杂度分析。对于O(n)的算法,10^8 差不多就是极限了,单个用例在1s左右,多个肯定超时。

代码

/**
 * @date 2024-08-15 0:22
 */
public class MaxScore3148 {

    /**
     * 修改了dp的定义,表示以i,j为起点到右下的矩形的最大值
     * 执行通过
     */
    public int maxScore_v2(List<List<Integer>> grid) {
        int m = grid.size();
        int n = grid.get(0).size();
        int[][] dp = new int[m][n];
        int res = Integer.MIN_VALUE;
        dp[m - 1][n - 1] = grid.get(m - 1).get(n - 1);
        for (int i = n - 2; i >= 0; i--) {
            int cur = grid.get(m - 1).get(i);
            // 注意这里使用的是上一个位置为顶点的矩形中的最大值
            res = Math.max(dp[m - 1][i + 1] - cur, res);
            // 如果先进行这个计算,然后再进行上面的计算,就有会出现不移动的情况,但积分只有移动才能取得,可以是负值
            dp[m - 1][i] = Math.max(cur, dp[m - 1][i + 1]);
        }
        for (int i = m - 2; i >= 0; i--) {
            int cur = grid.get(i).get(n - 1);
            res = Math.max(dp[i + 1][n - 1] - cur, res);
            dp[i][n - 1] = Math.max(cur, dp[i + 1][n - 1]);
        }

        for (int i = m - 2; i >= 0; i--) {
            for (int j = n - 2; j >= 0; j--) {
                int cur = grid.get(i).get(j);
                res = Math.max(dp[i + 1][j] - cur, res);
                res = Math.max(dp[i][j + 1] - cur, res);
                dp[i][j] = Math.max(cur, dp[i + 1][j]);
                dp[i][j] = Math.max(dp[i][j], dp[i][j + 1]);
            }
        }
        return res;
    }

    /**
     * 560 / 564 超时  m = 1000  n = 95
     * O(m * n * (m +n)) 循环次数 10^5 * (10^3 + 95)
     * 878 ms
     */
    public int maxScore_v1(List<List<Integer>> grid) {
        int m = grid.size();
        int n = grid.get(0).size();
        int[][] dp = new int[m][n];
        for (int[] row : dp) {
            Arrays.fill(row, Integer.MIN_VALUE);
        }
        dp[m - 1][n - 1] = 0;
        int res = Integer.MIN_VALUE;
        for (int i = n - 2; i >= 0; i--) {
            for (int j = i + 1; j < n; j++) {
                int diff = grid.get(m - 1).get(j) - grid.get(m - 1).get(i);
                if (dp[m - 1][j] > 0) {
                    dp[m - 1][i] = Math.max(diff + dp[m - 1][j], dp[m - 1][i]);
                }
                dp[m - 1][i] = Math.max(diff, dp[m - 1][i]);
            }
            res = Math.max(dp[m - 1][i], res);
        }
        for (int i = m - 2; i >= 0; i--) {
            for (int j = i + 1; j < m; j++) {
                int diff = grid.get(j).get(n - 1) - grid.get(i).get(n - 1);
                if (dp[j][n - 1] > 0) {
                    dp[i][n - 1] = Math.max(diff + dp[j][n - 1], dp[i][n - 1]);
                }
                dp[i][n - 1] = Math.max(diff, dp[i][n - 1]);
            }
            res = Math.max(dp[i][n - 1], res);
        }

        for (int i = m - 2; i >= 0; i--) {
            for (int j = n - 2; j >= 0; j--) {
                for (int h = i + 1; h < m; h++) {
                    int rowDiff = grid.get(h).get(j) - grid.get(i).get(j);
                    if (dp[h][j] > 0) {
                        dp[i][j] = Math.max(rowDiff + dp[h][j], dp[i][j]);
                    }
                    dp[i][j] = Math.max(rowDiff, dp[i][j]);
                }
                for (int k = j + 1; k < n; k++) {
                    int colDiff = grid.get(i).get(k) - grid.get(i).get(j);
                    if (dp[i][k] > 0) {
                        dp[i][j] = Math.max(colDiff + dp[i][k], dp[i][j]);
                    }
                    dp[i][j] = Math.max(colDiff, dp[i][j]);
                }
                res = Math.max(dp[i][j], res);
            }
        }
        return res;
    }

}

性能

1738.找出第K大的异或坐标值

目标

给你一个二维矩阵 matrix 和一个整数 k ,矩阵大小为 m x n 由非负整数组成。

矩阵中坐标 (a, b) 的 值 可由对所有满足 0 <= i <= a < m 且 0 <= j <= b < n 的元素 matrix[i][j](下标从 0 开始计数)执行异或运算得到。

请你找出 matrix 的所有坐标中第 k 大的值(k 的值从 1 开始计数)。

示例 1:

输入:matrix = [[5,2],[1,6]], k = 1
输出:7
解释:坐标 (0,1) 的值是 5 XOR 2 = 7 ,为最大的值。

示例 2:

输入:matrix = [[5,2],[1,6]], k = 2
输出:5
解释:坐标 (0,0) 的值是 5 = 5 ,为第 2 大的值。

示例 3:

输入:matrix = [[5,2],[1,6]], k = 3
输出:4
解释:坐标 (1,0) 的值是 5 XOR 1 = 4 ,为第 3 大的值。

示例 4:

输入:matrix = [[5,2],[1,6]], k = 4
输出:0
解释:坐标 (1,1) 的值是 5 XOR 2 XOR 1 XOR 6 = 0 ,为第 4 大的值。

说明:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 1000
  • 0 <= matrix[i][j] <= 10^6
  • 1 <= k <= m * n

思路

求二维矩阵 matrix 的第k大的异或坐标值,元素 matrix[i][j] 的异或坐标值等于对matrix[0][0] ~ matrix[i][j]矩阵中的所有值进行异或运算。

我们可以先分别对每一行按列进行异或运算,然后再针对每一列按行进行异或运算。然后将它们放入优先队列,再取第k大的值即可。

官网题解用到了二维前缀和 + 排序/快速选择。// todo

代码

/**
 * @date 2024-05-26 19:07
 */
public class KthLargestValue11738 {
    public int kthLargestValue(int[][] matrix, int k) {
        int m = matrix.length;
        int n = matrix[0].length;
        PriorityQueue<Integer> q = new PriorityQueue<>((a, b) -> b - a);
        int[][] dp = new int[m][n];
        for (int i = 0; i < m; i++) {
            dp[i][0] = matrix[i][0];
        }
        for (int i = 0; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i][j - 1] ^ matrix[i][j];
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 0; j < n; j++) {
                dp[i][j] ^= dp[i - 1][j];
                q.offer(dp[i][j]);
            }
        }
        for (int i = 0; i < n; i++) {
            q.offer(dp[0][i]);
        }
        int res = 0;
        while (k > 0) {
            res = q.poll();
            k--;
        }

        return res;
    }
}

性能