前缀和(矩阵区域和)(8)

https://blog.csdn.net/2601_95366422/article/details/158848006

上节课链接

一.题目

1314. 矩阵区域和 - 力扣(LeetCode)

二.思路

2.1 审题

题目要求对于矩阵中的每一个位置 (r, c) ,计算以它为中心、向四周各扩展 k 个单位得到的正方形区域内的所有元素之和。也就是说,行范围是从 r - kr + k ,列范围是从 c - kc + k

2.2 思路讲解
  • 下标偏移问题 :原始矩阵的下标是从 0 开始的,而我们在构建二维前缀和数组时,需要让下标从 1 开始,这样可以避免边界判断(因为 dp[0][*]dp[*][0] 可以初始化为 0)。因此,我们创建一个大小为 (m+1) × (n+1) 的前缀和数组 dp ,其中 dp[i][j] 表示原矩阵中从 (0,0)(i-1, j-1) 这个子矩阵的和。这样,原矩阵中位置 (r, c) 的元素对应到 dp 中的下标是 (r+1, c+1)

  • 前缀和计算公式 :构建 dp 时,使用二维前缀和递推公式:
    dp[i+1][j+1] = dp[i+1][j] + dp[i][j+1] - dp[i][j] + mat[i][j]

    这里 ij 遍历原矩阵的行和列(从 0 到 m-1, 0 到 n-1),这样计算出的 dp[i+1][j+1] 正好对应原矩阵从 (0,0) 到 (i,j) 的和。

  • 查询子矩阵和 :对于每个位置 (i, j) ,我们需要计算以它为中心、边长为 2k+1 的正方形区域的和。但由于边界可能越界,实际范围是:

    行范围:r1 = max(0, i - k)r2 = min(m - 1, i + k)

    列范围:c1 = max(0, j - k)c2 = min(n - 1, j + k)

    为了利用前缀和数组,我们将这些边界转换为 dp 中的下标:
    x1 = r1 + 1y1 = c1 + 1x2 = r2 + 1y2 = c2 + 1

    那么对应的子矩阵和可以通过前缀和快速得到:
    sum = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1]

三.代码演示

cpp 复制代码
class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) 
    {
        int m = mat.size();
        int n = mat[0].size();
        vector<vector<int>> dp(m + 1,vector<int>(n + 1));
        vector<vector<int>> answer(m,vector<int>(n));
        //求二维前缀和
        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                dp[i + 1][j + 1] = dp[i + 1][j] + dp[i][j + 1] - dp[i][j] + mat[i][j]; 
            }
        }    
        //使用二维前缀和
        for(int i = 0;i < m;i++)
        {
            for(int j = 0; j < n;j++)
            {
                int x1 = max(0,i - k) + 1,y1 = max(0,j - k) + 1;
                int x2 = min(m - 1,i + k) + 1,y2 = min(n - 1,j + k) + 1;
                answer[i][j] = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];
            }
        }
        return answer;
    }
};

四.代码讲解

一、初始化前缀和数组

为了高效计算任意子矩阵的和,我们首先构建一个二维前缀和数组 dp。由于原矩阵下标从 0 开始,而前缀和公式通常需要下标从 1 开始以简化边界处理,因此我们创建一个大小为 (m+1) × (n+1) 的数组 dp,其中 mn 分别为原矩阵的行数和列数。dp[i][j] 表示原矩阵中从左上角 (0,0)(i-1, j-1) 这个矩形区域的所有元素之和。这样,dp[0][*]dp[*][0] 自动为 0,为后续计算提供了安全保障。

二、构建二维前缀和

我们通过两层循环遍历原矩阵的每一个元素 mat[i][j]i 从 0 到 m-1,j 从 0 到 n-1),并按照二维前缀和递推公式 更新 dp

  • dp[i+1][j+1] = dp[i+1][j] + dp[i][j+1] - dp[i][j] + mat[i][j] 这个公式的几何意义是:当前位置 (i+1, j+1) 对应的矩形和等于左边矩形dp[i+1][j])加上右边矩形dp[i][j+1]),再减去重复计算的左上角矩形dp[i][j]),最后加上当前元素 mat[i][j] 。通过一次双重循环,我们就能得到所有前缀和,时间复杂度 O(m×n)
三、查询每个位置的区域和

题目要求对于每个位置 (i, j),计算以它为中心、向四周扩展 k 个单位得到的正方形区域的和。由于可能越界,我们需要先确定实际的行列范围:

  • 行范围: r1 = max(0, i - k)r2 = min(m - 1, i + k)

  • 列范围: c1 = max(0, j - k)c2 = min(n - 1, j + k)

为了使用前缀和数组,将这些边界转换为 dp 中的下标(因为 dp 的下标比原矩阵大 1):

  • x1 = r1 + 1y1 = c1 + 1

  • x2 = r2 + 1y2 = c2 + 1 那么子矩阵的和可以通过容斥原理快速得到:

  • sum = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1] 这里,d**p[x2][y2]**是从左上角到 (r2, c2) 的大矩形和减去左边矩形 dp[x2][y1-1]和右边矩形 dp[x1-1][y2] 和,再加回被重复减去的左上角矩形 dp[x1-1][y1-1]

相关推荐
Vect__2 小时前
基于CSAPP深刻理解编译链接过程
linux·c++
月落归舟2 小时前
排序算法---(一)
数据结构·算法·排序算法
liuyao_xianhui2 小时前
优选算法_翻转链表_头插法_C++
开发语言·数据结构·c++·算法·leetcode·链表·动态规划
福昕办公2 小时前
C++ 无原生 JSON 支持?一文实现通用序列化与反序列化封装方案
c++
Book思议-2 小时前
【数据结构实战】循环队列FIFO 特性生成六十甲子(天干地支纪年法),实现传统文化里的 “时间轮回”
数据结构·算法·
im_AMBER2 小时前
Leetcode 147 零钱兑换 | 单词拆分
javascript·学习·算法·leetcode·动态规划
zl_vslam2 小时前
SLAM中的非线性优-3D图优化之IMU预积分SE3推导(二十一)
人工智能·算法·计算机视觉·3d
JAVA+C语言2 小时前
C++ STL map 系列全方位解析
开发语言·c++
c++逐梦人2 小时前
DFS经典例题(八皇后,数独)
算法·蓝桥杯·深度优先