当前位置: 首页 > news >正文

2025.11.14 力扣每日一题

2536.子矩阵元素加1

差分数组这个是真的有点难理解,琢磨好长时间。。。

#include <vector>
using namespace std;class Solution {
public:vector<vector<int>> rangeAddQueries(int n, vector<vector<int>>& queries) {// 1. 初始化二维差分数组// 大小为 (n+2)×(n+2),额外的两行两列是为了避免边界判断(防止数组越界)vector<vector<int>> diff(n + 2, vector<int>(n + 2, 0));// 2. 处理所有查询,在差分数组中标记子矩阵加1的操作for (auto & q : queries) {// 提取查询的边界:左上角(r1,c1),右下角(r2,c2)int r1 = q[0], c1 = q[1], r2 = q[2], c2 = q[3];// 核心:对差分数组的四个角落进行标记,实现"子矩阵加1"的差分记录// 标记1:子矩阵左上角的右下角位置 +1(表示从这里开始加1)diff[r1 + 1][c1 + 1]++;// 标记2:子矩阵右上角的右侧位置 -1(表示列方向结束加1)diff[r1 + 1][c2 + 2]--;// 标记3:子矩阵左下角的下方位置 -1(表示行方向结束加1)diff[r2 + 2][c1 + 1]--;// 标记4:子矩阵右下角的右下方位置 +1(抵消重复减去的部分,保证区域精确性)diff[r2 + 2][c2 + 2]++;}// 3. 计算二维前缀和,将差分数组转化为最终结果矩阵vector<vector<int>> ans(n, vector<int>(n, 0));  // 存储最终结果的n×n矩阵// 遍历原矩阵的每个位置(i,j)(0-based)for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {// 计算差分数组在(i+1,j+1)位置的二维前缀和// 公式:当前值 += 左方前缀和 + 上方前缀和 - 左上角重叠区域前缀和// 这一步会将之前的"角落标记"扩散到整个矩阵,得到每个位置的总加1次数diff[i + 1][j + 1] += diff[i + 1][j] + diff[i][j + 1] - diff[i][j];// 将计算结果存入答案矩阵(差分数组的(i+1,j+1)对应原矩阵的(i,j))ans[i][j] = diff[i + 1][j + 1];}}// 返回最终矩阵return ans;}
};

差分数组(Difference Array)知识点总结

1. 定义与核心思想
  • 定义:差分数组是一种用于高效处理区间更新的数据结构,通过记录 “相邻元素的差值” 来简化多次区间增减操作。
  • 核心思想:将 “对区间 [l, r] 内所有元素加 / 减 val” 的操作,转化为对差分数组的两个端点的修改,最终通过前缀和还原出原数组。
2. 一维差分数组
(1)构造方法

对于原数组 arr(长度为 n),差分数组 diff 定义为:

  • diff[0] = arr[0]
  • diff[i] = arr[i] - arr[i-1]i ≥ 1
(2)区间更新操作

若要对区间 [l, r] 内所有元素加 val,只需修改差分数组的两个位置:

diff[l] += val;       // 从 l 开始的元素都加 val
if (r + 1 < n) {diff[r + 1] -= val;  // 从 r+1 开始的元素抵消加 val(恢复原状)
}
(3)还原原数组

对差分数组 diff 计算前缀和,即可得到更新后的原数组:

arr[0] = diff[0];
for (int i = 1; i < n; i++) {arr[i] = arr[i-1] + diff[i];
}
3. 二维差分数组
(1)适用场景

用于二维矩阵的子矩阵区间更新(如对 (r1,c1)~(r2,c2) 内所有元素加 val)。

(2)区间更新操作

对二维差分数组 diff 的四个角落进行标记,即可实现子矩阵更新:

// 对以 (r1,c1) 为左上角、(r2,c2) 为右下角的子矩阵加 val
diff[r1][c1] += val;
diff[r1][c2 + 1] -= val;  // 列方向结束标记
diff[r2 + 1][c1] -= val;  // 行方向结束标记
diff[r2 + 1][c2 + 1] += val;  // 抵消重复标记
  • 注:实际实现中通常将 diff 大小设为 (n+2)×(n+2),避免边界越界判断。
(3)还原原矩阵

对二维差分数组 diff 计算二维前缀和,即可得到更新后的矩阵:

// 二维前缀和递推公式
diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1];
// 此时 diff[i][j] 即为原矩阵 (i,j) 位置的最终值
4. 优势与适用场景
  • 优势:将多次区间更新的时间复杂度从 O(k·m)k 为操作次数,m 为区间长度)优化为 O(k + n)(一维)或 O(k + n²)(二维),大幅提升效率。
  • 适用场景
    • 频繁对连续区间 / 子矩阵进行加 / 减操作。
    • 最终需要获取每个元素的具体值(而非仅统计区间和)。
5. 与前缀和的关系
  • 互为逆操作
    • 前缀和:将原数组转化为 “前缀和数组”,用于快速计算区间和。
    • 差分数组:将原数组转化为 “差分标记”,用于快速执行区间更新,再通过前缀和还原。
  • 流程:原数组 → 差分数组(记录更新) → 前缀和 → 更新后的原数组。
6. 典型例题
  • 一维:LeetCode 1109. 航班预订统计(区间增减后求最终座位数)。
  • 二维:LeetCode 2536. 子矩阵元素加 1(多次子矩阵加 1 后求最终矩阵)。
总结

差分数组是处理 “区间更新” 问题的高效工具,核心在于通过 “端点标记” 替代 “全区间修改”,再利用前缀和还原结果。掌握一维和二维差分数组的标记规则和还原方法,可快速解决各类区间更新问题。

前缀和(Prefix Sum)知识点总结

前缀和是一种用于快速计算数组 / 矩阵区间和的数据结构,核心是通过 “预处理前缀和数组”,将任意区间和的查询时间复杂度从 O(n) 优化到 O(1),是算法中处理 “区间求和” 问题的基础工具。

一、核心定义与核心思想

1. 定义

前缀和数组的每个元素,代表原数组从起始位置到当前位置的所有元素之和

  • 一维场景:前缀和数组 preSum[i] = 原数组 arr[0] + arr[1] + ... + arr[i-1](通常前缀和数组比原数组长 1,preSum[0] = 0 作为哨兵)。
  • 二维场景:前缀和数组 preSum[i][j] = 原矩阵 mat[0][0] + ... + mat[i-1][j-1](左上角到 (i-1,j-1) 的所有元素和)。

2. 核心思想

通过预处理一次前缀和数组,将 “多次区间和查询” 转化为 “前缀和数组的差值计算”,避免重复遍历原数组,提升查询效率。

二、一维前缀和(最基础场景)

1. 预处理前缀和数组

给定原数组 arr(长度为 n),构造前缀和数组 preSum(长度为 n+1):

vector<int> preSum(n + 1, 0);
for (int i = 0; i < n; i++) {preSum[i + 1] = preSum[i] + arr[i];  // 递推公式:前i+1个元素和 = 前i个元素和 + 当前元素
}

2. 快速查询区间和

查询原数组区间 [l, r](0-based,包含 l 和 r)的和:

int sum = preSum[r + 1] - preSum[l];  // 核心公式:区间和 = 前r+1个和 - 前l个和

示例

  • 原数组 arr = [1, 2, 3, 4, 5]
  • 前缀和数组 preSum = [0, 1, 3, 6, 10, 15]
  • 查询 [1, 3](元素 2+3+4):preSum[4] - preSum[1] = 10 - 1 = 9,结果正确。

三、二维前缀和(矩阵区间求和)

1. 适用场景

快速计算二维矩阵中 “任意子矩阵的和”(如左上角 (x1,y1) 到右下角 (x2,y2) 的元素和)。

2. 预处理前缀和数组

给定原矩阵 matn×m 大小),构造前缀和数组 preSum(n+1)×(m+1) 大小):

vector<vector<int>> preSum(n + 1, vector<int>(m + 1, 0));
for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {// 递推公式:利用容斥原理,避免重复计算重叠区域preSum[i + 1][j + 1] = preSum[i][j + 1] + preSum[i + 1][j] - preSum[i][j] + mat[i][j];}
}
  • 拆解公式:
    • preSum[i][j+1]:上方区域的前缀和
    • preSum[i+1][j]:左方区域的前缀和
    • preSum[i][j]:上方 + 左方的重叠区域(被重复加了两次,需减去)
    • mat[i][j]:当前位置的元素值

3. 快速查询子矩阵和

查询子矩阵 (x1,y1) ~ (x2,y2)(0-based,包含边界)的和:

int sum = preSum[x2 + 1][y2 + 1] - preSum[x1][y2 + 1] - preSum[x2 + 1][y1] + preSum[x1][y1];
  • 拆解公式:
    • preSum[x2+1][y2+1]:整个矩阵到 (x2,y2) 的前缀和
    • 减去 preSum[x1][y2+1]:上方不需要的区域和
    • 减去 preSum[x2+1][y1]:左方不需要的区域和
    • 加上 preSum[x1][y1]:被重复减去的重叠区域和(需补回)

示例

  • 原矩阵 mat = [[1,2],[3,4]]
  • 前缀和数组 preSum = [[0,0,0],[0,1,3],[0,4,10]]
  • 查询子矩阵 (0,0)~(1,1)(全矩阵和):preSum[2][2] - preSum[0][2] - preSum[2][0] + preSum[0][0] = 10 - 0 - 0 + 0 = 10,结果正确(1+2+3+4=10)。

四、核心性质与优势

  1. 时间复杂度:预处理 O(n)(一维)或 O(n×m)(二维),单次查询 O(1),多次查询时优势极明显。
  2. 空间复杂度O(n)(一维)或 O(n×m)(二维),需额外存储前缀和数组。
  3. 无修改场景:前缀和适用于 “原数组 / 矩阵不修改,仅查询区间和” 的场景;若有频繁修改,需用树状数组或线段树。

五、典型例题

  1. 一维:LeetCode 303. 区域和检索 - 数组不可变(基础区间和查询)。
  2. 二维:LeetCode 304. 二维区域和检索 - 矩阵不可变(基础子矩阵和查询)。
  3. 拓展:LeetCode 560. 和为 K 的子数组(前缀和 + 哈希表,统计满足条件的子数组个数)。

六、与差分数组的关系

  • 互为逆操作
    • 前缀和:原数组 → 前缀和数组(用于快速查询区间和)。
    • 差分数组:原数组 → 差分数组(用于快速执行区间更新),再通过前缀和还原原数组。
  • 组合使用:区间更新 + 区间查询 场景(先差分更新,再前缀和查询)。

总结

前缀和的核心价值是 “预处理换快速查询”,是处理 “静态区间和” 问题的最优工具。掌握一维和二维前缀和的构造公式与查询公式,可快速解决各类区间求和问题,是算法入门的必学知识点。

前缀和核心公式速查表

这份速查表涵盖一维、二维前缀和的核心公式、代码模板及典型例题,方便快速查阅和直接套用。

一、一维前缀和(核心公式 + 模板)

1. 核心公式

  • 预处理前缀和数组:preSum[i+1] = preSum[i] + arr[i]preSum[0] = 0,哨兵简化计算)
  • 区间和查询(原数组 [l, r],0-based,含边界):sum = preSum[r+1] - preSum[l]

2. 代码模板

// 1. 预处理前缀和
vector<int> getPrefixSum(vector<int>& arr) {int n = arr.size();vector<int> preSum(n + 1, 0);for (int i = 0; i < n; i++) {preSum[i + 1] = preSum[i] + arr[i];}return preSum;
}// 2. 查询区间 [l, r] 的和
int queryRangeSum(vector<int>& preSum, int l, int r) {return preSum[r + 1] - preSum[l];
}

3. 典型例题(LeetCode 303. 区域和检索 - 数组不可变)

class NumArray {
private:vector<int> preSum;
public:NumArray(vector<int>& nums) {int n = nums.size();preSum.resize(n + 1, 0);for (int i = 0; i < n; i++) {preSum[i + 1] = preSum[i] + nums[i];}}int sumRange(int left, int right) {return preSum[right + 1] - preSum[left];}
};

二、二维前缀和(核心公式 + 模板)

1. 核心公式

  • 预处理前缀和数组:preSum[i+1][j+1] = preSum[i][j+1] + preSum[i+1][j] - preSum[i][j] + mat[i][j]preSum[0][*] = preSum[*][0] = 0
  • 子矩阵和查询(原矩阵 (x1,y1)~(x2,y2),0-based,含边界):sum = preSum[x2+1][y2+1] - preSum[x1][y2+1] - preSum[x2+1][y1] + preSum[x1][y1]

2. 代码模板

// 1. 预处理前缀和
vector<vector<int>> get2DPrefixSum(vector<vector<int>>& mat) {int n = mat.size(), m = mat[0].size();vector<vector<int>> preSum(n + 1, vector<int>(m + 1, 0));for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {preSum[i + 1][j + 1] = preSum[i][j + 1] + preSum[i + 1][j] - preSum[i][j] + mat[i][j];}}return preSum;
}// 2. 查询子矩阵 (x1,y1)~(x2,y2) 的和
int query2DRangeSum(vector<vector<int>>& preSum, int x1, int y1, int x2, int y2) {return preSum[x2 + 1][y2 + 1] - preSum[x1][y2 + 1] - preSum[x2 + 1][y1] + preSum[x1][y1];
}

3. 典型例题(LeetCode 304. 二维区域和检索 - 矩阵不可变)

class NumMatrix {
private:vector<vector<int>> preSum;
public:NumMatrix(vector<vector<int>>& matrix) {int n = matrix.size(), m = matrix[0].size();preSum.resize(n + 1, vector<int>(m + 1, 0));for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {preSum[i + 1][j + 1] = preSum[i][j + 1] + preSum[i + 1][j] - preSum[i][j] + matrix[i][j];}}}int sumRegion(int row1, int col1, int row2, int col2) {return preSum[row2 + 1][col2 + 1] - preSum[row1][col2 + 1] - preSum[row2 + 1][col1] + preSum[row1][col1];}
};

三、拓展场景(前缀和 + 哈希表)

核心公式

  • 前缀和差值:preSum[i] - preSum[j] = k → 子数组 [j+1, i] 的和为 k
  • 哈希表存储:map[preSum_val] = 出现次数,统计满足条件的子数组个数

典型例题(LeetCode 560. 和为 K 的子数组)

class Solution {
public:int subarraySum(vector<int>& nums, int k) {unordered_map<int, int> mp;mp[0] = 1;  // 初始化:前缀和为0出现1次int preSum = 0, count = 0;for (int num : nums) {preSum += num;// 查找是否存在 preSum - k,存在则累加次数if (mp.count(preSum - k)) {count += mp[preSum - k];}mp[preSum]++;  // 记录当前前缀和出现次数}return count;}
};

四、关键注意点

  1. 下标统一:前缀和数组比原数组 / 矩阵多一行一列(哨兵),避免边界判断。
  2. 容斥原理:二维前缀和的预处理和查询均需用到容斥,避免重复计算或漏算。
  3. 适用场景:仅适用于 “原数据不修改,仅查询区间和” 的静态场景。

其余后面再做。。。今天先到这儿

http://www.dtcms.com/a/613725.html

相关文章:

  • LDO输出电容、磁珠导致的纹波异常以及ADC有效位测量学习
  • 典型的 ROS 2 ament_cmake构建CMake脚本中ament相关指令解释
  • 【高级机器学习】 14. 多任务学习
  • 函数式接口+default接口+springAi 中的ducumentReader去理解为什么存在default接口的形式
  • 购物网站备案水友做的yyf网站
  • 专业建网站设计公司潍坊建设网站公司
  • linux之ubuntu qt界面开发开发点菜系统
  • 海东市公司网站建设介绍自己的家乡遵义网站建设
  • GitHub 热榜项目 - 日榜(2025-11-15)
  • 测开学习DAY29
  • 2.常用软件记录汇总
  • L2 RLC层介绍:架构、数据封装与模式
  • 长沙网站建设 网站设计安全的合肥网站建设
  • 凡科做网站行吗专业网站推广优化
  • 物联网控制|计算机控制-刘川来胡乃平版|第4章:过程通道与人机接口-4.4Human-Machine Interface|课堂笔记|
  • 请求签名(Request Signature)
  • 从零开始构建企业级物联网平台:IoTSharp 架构设计与实践全解析
  • FFmpeg解码音频数据AudioTrack/OpenSL播放
  • 怎么做英文网站wordpress go跳转页面
  • 下载 asp网站手工制作衣服童装环保
  • 重估百度,也是在重估 AI 的未来
  • 网页版C语言编译器:基于Web平台的C语言编译与执行环境优化
  • 网站名称在哪里修改长春自助建站软件
  • 43_FastMCP 2.x 中文文档之FastMCP集成:AuthKit 认证指南
  • MYSQL的三大范式
  • 电商系统中超卖和重复下单问题思考
  • 抽象类VS接口:核心区别与实战选择
  • CSDN博客写作技巧整理
  • 18.【NXP 号令者RT1052】开发——实战-电容触摸按键
  • 三种硬盘检测工具推荐CrytalDiskMark ,DiskGenius,AS SSD Benchmark