算法27,二维前缀和

有点难

这份笔记详细记录了二维前缀和 的原理推导,这是算法竞赛和面试中非常经典的技巧。它主要用于解决**"快速计算矩阵(二维数组)中某子矩阵的元素和"**的问题。

1. 核心思想:降维打击

一维数组求区间和,我们用一维前缀和

二维数组(矩阵)求子矩阵和,自然升级为二维前缀和

  • 暴力法:每次查询都遍历子矩阵,复杂度 O(m×n)。

  • 前缀和法 :通过空间换时间,预处理出 dp数组,使得单次查询复杂度降为 O(1)。

2. 推导过程解析(看图说话)

(1) 定义:dpij是什么?

笔记中明确定义:dpij表示从 (1,1)到 (i,j)这个矩形区域内所有元素的和

也就是以左上角为原点,右下角为 (i,j)的子矩阵的总和。

(2) 状态转移方程(最难理解的一步)

笔记中画了一个 2×2的小矩阵,标记为 A、B、C、D,这是推导的关键:

  • 目标 :求右下角的块 D

  • 已知

    • dpi−1j:包含了 A、B 的大矩形和。

    • dpij−1:包含了 A、C 的大矩形和。

    • arrij:就是 D 的值。

    • dpi−1j−1:包含了 A 的矩形和(多加了一次,需要减掉)。

推导逻辑

如果我们把上面两个大矩形加起来(dpi−1j+dpij−1),中间的 A 区域被加了两次

所以,我们需要减去一次 A(即 dpi−1j−1),再加上当前格子的右下角 D(即 arrij)。

得出公式

复制代码
dp[i][j]=dp[i−1][j]+dp[i][j−1]+arr[i][j]−dp[i−1][j−1]

(注:这个公式的前提是输入数组 arr的下标也从 1 开始,且 dp[0][x]dp[x][0]都为 0)

3. 查询逻辑(容斥原理)

笔记下半部分画了一个大方框套小方框的图,用来解释如何查询任意子矩阵 (x1​,y1​)到 (x2​,y2​)的和。

类比一维前缀和(suml...r=dpr−dpl−1),二维查询就是"大盒子减小盒子加重叠":

复制代码
Sum=dp[x2​][y2​]−dp[x1​−1][y2​]−dp[x2​][y1​−1]+dp[x1​−1][y1​−1]
  • dpx2​y2​:包含目标区域的超大矩形总面积。

  • 减去左边多余列 (dpx1​−1y2​) ​ 和 上边多余行 (dpx2​y1​−1)

  • 加上左上角多减的一次 (dpx1​−1y1​−1)

4. 代码实现(Java 版)

结合笔记中的逻辑,以下是标准的 Java 实现代码:

复制代码
public class TwoDPrefixSum {
    public static void main(String[] args) {
        // 假设输入数据如下(下标从1开始,补0方便计算)
        // 3 4 表示 3行 4列
        int[][] arr = {
            {0, 0, 0, 0, 0}, // 第0行填充0,作为哨兵节点
            {0, 1, 2, 3, 4}, // 第1行数据
            {0, 5, 6, 7, 8}, // 第2行数据
            {0, 9, 10, 11, 12} // 第3行数据
        };
        
        int m = 3, n = 4;
        long[][] dp = new long[m + 1][n + 1];

        // 1. 预处理(构建 dp 数组)
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                // 套用笔记中的公式
                dp[i][j] = dp[i-1][j] + dp[i][j-1] + arr[i][j] - dp[i-1][j-1];
            }
        }

        // 2. 查询示例:求 (2,2) 到 (3,3) 的子矩阵和
        int x1 = 2, y1 = 2;
        int x2 = 3, y2 = 3;
        
        long result = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1];
        
        System.out.println("子矩阵和为: " + result); // 预期结果:6+7+10+11 = 34
    }
}

5. 总结与避坑指南

  1. 下标统一 :笔记左侧问"为什么下标从1开始?",这是因为这样可以完美避开数组越界,并且让 dp[0][...]dp[...][0]自然变为 0,简化计算。

  2. 数据类型 :笔记中特意将 dp数组声明为 long类型。这是因为矩阵求和很容易发生整数溢出 ,如果输入数据是 int且矩阵很大,求和结果可能会超过 int的最大值,必须使用 long

  3. 复杂度

    • 预处理:O(m×n)

    • 单次查询:O(1)

相关推荐
花酒锄作田4 小时前
Pydantic校验配置文件
python
hboot4 小时前
AI工程师第四课 - 深度学习入门
pytorch·python·神经网络
罗西的思考7 小时前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
美团技术团队10 小时前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
ZhengEnCi15 小时前
P2M-Matplotlib折线图完全指南-从数据可视化到趋势分析的Python绘图利器
python·matlab·数据可视化
ZhengEnCi16 小时前
P2L-Matplotlib饼图完全指南-从数据可视化到图表定制的Python绘图利器
python·matlab
曲幽16 小时前
你的REST接口还在“过度投喂”数据吗?——FastAPI + GraphQL实战避坑指南
python·fastapi·web·graphql·route·cors·rest·strawberry
用户83580861879117 小时前
基于 Self-RAG 与列表级重排序的进阶 RAG 系统设计与实现
python
To_OC1 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode
To_OC1 天前
LC 208 实现 Trie 前缀树:曾被名字劝退,写完发现是送分题
javascript·算法·leetcode