力扣2536子矩阵元素加1-差分数组解法详解

问题描述

给你一个正整数 n,表示最初有一个 n x n 的矩阵,每个元素都是 0。

给你一个二维整数数组 queries,其中 queries[i] = [row1, col1, row2, col2]。对于每个查询 i,需要找到所有满足 row1 <= x <= row2 且 col1 <= y <= col2 的位置 (x, y),并将这个位置上的元素加 1。

返回执行完所有查询后得到的矩阵。

暴力解法的问题

如果直接模拟,对每个查询都遍历子矩阵中的每个元素进行加1:

go 复制代码
for _, q := range queries {
    for i := q[0]; i <= q[2]; i++ {
        for j := q[1]; j <= q[3]; j++ {
            matrix[i][j]++
        }
    }
}

时间复杂度为 O(queries * n^2),在最坏情况下约为 500^2 * 10^4 = 2.5e9 操作,必然超时。

差分数组的第一性原理

一维差分数组的本质

假设有一个数组 a = [3, 5, 2, 6],我们构造它的差分数组 diff:

text 复制代码
a    = [3, 5, 2, 6]
diff = [3, 2, -3, 4]

其中 diff[i] = a[i] - a[i-1](假设 a[-1] = 0):

  • diff[0] = 3 - 0 = 3
  • diff[1] = 5 - 3 = 2
  • diff[2] = 2 - 5 = -3
  • diff[3] = 6 - 2 = 4

关键观察:原数组可以通过差分数组的前缀和还原:

text 复制代码
a[0] = diff[0] = 3
a[1] = diff[0] + diff[1] = 3 + 2 = 5
a[2] = diff[0] + diff[1] + diff[2] = 3 + 2 + (-3) = 2
a[3] = diff[0] + diff[1] + diff[2] + diff[3] = 3 + 2 + (-3) + 4 = 6

即 a[i] = sum(diff[0..i])。

为什么区间更新只需要修改两个端点?

假设要对区间 [l, r] 的所有元素加 val,直接修改需要 O(r-l+1) 次操作。

但在差分数组中,我们只需:

  • diff[l] += val
  • diff[r+1] -= val

为什么这样有效?

因为差分数组的前缀和还原性质:

  • 对于 i < l:前缀和不受影响,a[i] 不变
  • 对于 l <= i <= r:前缀和会累加 diff[l] 的增量 val,所以 a[i] += val
  • 对于 i > r:前缀和累加了 diff[l] 的 +val 和 diff[r+1] 的 -val,两者抵消,a[i] 不变

这就是差分数组的精髓:通过标记边界,利用前缀和的累积效应,实现区间更新。

二维差分数组的推导

二维情况下,我们要对子矩阵 (r1,c1) 到 (r2,c2) 的所有元素加 val。

类比一维,我们需要在差分矩阵 diff 中标记边界,使得二维前缀和还原时,恰好只有目标子矩阵的元素被加上 val。

二维前缀和公式:

text 复制代码
result[i][j] = result[i-1][j] + result[i][j-1] - result[i-1][j-1] + diff[i][j]

要让 (r1,c1) 到 (r2,c2) 区域加 val,我们需要:

  1. 在 diff[r1][c1] 加 val :从 (r1,c1) 开始,右下方所有位置都会累加这个值
  2. 在 diff[r1][c2+1] 减 val :阻止 c2 列右侧继续累加
  3. 在 diff[r2+1][c1] 减 val :阻止 r2 行下方继续累加
  4. 在 diff[r2+1][c2+1] 加 val:补偿右下角被重复减去的值

这四个操作确保只有目标矩形区域受到影响。

为什么是这四个位置?

让我们用容斥原理理解:

  • diff[r1][c1] += val:影响范围是从 (r1,c1) 到 (n-1,n-1) 的整个右下区域
  • diff[r1][c2+1] -= val:减去从 (r1,c2+1) 到右下的区域,排除右侧超出部分
  • diff[r2+1][c1] -= val:减去从 (r2+1,c1) 到右下的区域,排除下方超出部分
  • diff[r2+1][c2+1] += val:加回 (r2+1,c2+1) 到右下的区域,因为它被减了两次

通过容斥,最终只有 (r1,c1) 到 (r2,c2) 的矩形区域受到 +val 的影响。

完整示例演示

假设 n=3,queries = \[0,0,1,1],即对子矩阵 (0,0) 到 (1,1) 加1。

步骤1:初始化差分数组

text 复制代码
diff:
0 0 0
0 0 0
0 0 0

步骤2:处理查询 0,0,1,1

按照四个操作:

  1. diff[0][0] += 1
text 复制代码
1 0 0
0 0 0
0 0 0
  1. diff[0][2] -= 1 (c2+1=2)
text 复制代码
1 0 -1
0 0 0
0 0 0
  1. diff[2][0] -= 1 (r2+1=2)
text 复制代码
1 0 -1
0 0 0
-1 0 0
  1. diff[2][2] += 1 (r2+1=2, c2+1=2)
text 复制代码
1 0 -1
0 0 0
-1 0 1

步骤3:二维前缀和还原

逐个计算 result[i][j] = result[i-1][j] + result[i][j-1] - result[i-1][j-1] + diff[i][j]:

  • result[0][0] = 0 + 0 - 0 + 1 = 1
  • result[0][1] = 0 + 1 - 0 + 0 = 1
  • result[0][2] = 0 + 1 - 0 + (-1) = 0
  • result[1][0] = 1 + 0 - 0 + 0 = 1
  • result[1][1] = 1 + 1 - 1 + 0 = 1
  • result[1][2] = 1 + 1 - 1 + 0 = 0
  • result[2][0] = 1 + 0 - 0 + (-1) = 0
  • result[2][1] = 0 + 1 - 1 + 0 = 0
  • result[2][2] = 0 + 0 - 0 + 1 = 0

最终矩阵

text 复制代码
1 1 0
1 1 0
0 0 0

完美!只有 (0,0) 到 (1,1) 的子矩阵被加1了。

代码实现

go 复制代码
func rangeAddQueries(n int, queries [][]int) [][]int {
    // 创建差分数组
    diff := make([][]int, n+1)
    for i := range diff {
        diff[i] = make([]int, n+1)
    }
    
    // 处理每个查询,在差分数组中标记
    for _, q := range queries {
        r1, c1, r2, c2 := q[0], q[1], q[2], q[3]
        diff[r1][c1]++
        if r2+1 < n {
            diff[r2+1][c1]--
        }
        if c2+1 < n {
            diff[r1][c2+1]--
        }
        if r2+1 < n && c2+1 < n {
            diff[r2+1][c2+1]++
        }
    }
    
    // 计算二维前缀和,得到最终矩阵
    result := make([][]int, n)
    for i := range result {
        result[i] = make([]int, n)
    }
    
    for i := 0; i < n; i++ {
        for j := 0; j < n; j++ {
            // result[i][j] = result[i-1][j] + result[i][j-1] - result[i-1][j-1] + diff[i][j]
            if i > 0 {
                result[i][j] += result[i-1][j]
            }
            if j > 0 {
                result[i][j] += result[i][j-1]
            }
            if i > 0 && j > 0 {
                result[i][j] -= result[i-1][j-1]
            }
            result[i][j] += diff[i][j]
        }
    }
    
    return result
}
相关推荐
梦帮科技几秒前
脑机接口(BCI)与神经系统安全:EEG 神经电信号欺骗注入、神经闭环解码算法与生物黑客反制
算法·安全
老徐聊GEO1 分钟前
把“安徽“写进题面之后:多平台AI推荐名单的区域扰动测量(2026)
前端·人工智能·算法·geo优化
人工智能培训2 分钟前
具身智能:让人工智能拥有物理世界感知能力
大数据·人工智能·学习·算法·机器学习
果壳science3 分钟前
张祥前最新研究成果登上SCI期刊!
笔记·嵌入式硬件·算法·硬件工程·几何学
Rabitebla4 分钟前
【Linux 底层原理】工具篇(一):掌握 Ubuntu apt,让软件安装与管理更简单
linux·笔记·算法·ubuntu
利威尔xu25 分钟前
Go IM 源码实战(2):WebSocket 握手 11 步,少 1 个参数连不上
后端·websocket·微服务·面试·golang
碧海银沙音频科技研究院32 分钟前
基于炬芯ATS308X的DNN全连接神经网络手写数字识别系统设计
人工智能·深度学习·算法
文丑颜不良啊39 分钟前
LeetCode601~625题目解析
java·leetcode
stolentime1 小时前
SOS DP学习笔记
c++·笔记·学习·算法·贪心算法·动态规划·二进制
文丑颜不良啊1 小时前
LeetCode651~675题目解析
java·leetcode