第 1 章:数组与字符串(2) --- 前缀和 & 差分数组
一维前缀和(Prefix Sum)
✅ 定义
前缀和用于快速求解数组中某段区间 [l, r] 的和。
前缀和数组 sum 定义为:
sum[0] = 0;
sum[i + 1] = sum[i] + nums[i]; // sum[i+1] 表示 nums[0] 到 nums[i] 的和
求区间和:
// 区间 [l, r] 的和
int rangeSum = sum[r + 1] - sum[l];
为什么是 sum[r+1] - sum[l]?
-
sum[r+1]包含了 nums[0] 到 nums[r] 的和。 -
sum[l]包含了 nums[0] 到 nums[l-1] 的和。 -
所以两者相减,就得到了 nums[l] 到 nums[r] 的区间和。
🧱 模板代码(C++)
vector<int> prefixSum(const vector<int>& nums)
{
int n = nums.size();
vector<int> sum(n + 1, 0);
for (int i = 0; i < n; ++i)
{
sum[i + 1] = sum[i] + nums[i];
}
return sum;
}
🧪 示例题目
-
- 区域和检索 - 数组不可变
-
- 和为K的子数组
一维差分数组(Difference Array)
✅ 定义与作用
差分数组是前缀和的逆操作,它适用于:频繁修改区间的数值,但不需要频繁查询原始值的场景。
原数组 nums 转换为差分数组 diff:
diff[0] = nums[0];
diff[i] = nums[i] - nums[i - 1];
恢复原数组:
nums[0] = diff[0];
nums[i] = nums[i - 1] + diff[i];
🤔 为什么差分可以快速区间加值?
要把某个区间 [l, r] 中的每个值都加上 val:
我们可以仅做:
diff[l] += val;
diff[r+1] -= val;
这样,在恢复原数组时,会自动把区间 [l, r] 中的值全部加上 val。
原理推导:
设初始数组为 [0, 0, 0, 0, 0],差分数组也是全 0。
现在我们想将区间 [1, 3] 加上 5:
diff[1] += 5; // 从位置1开始,加5
diff[4] -= 5; // 从位置4之后开始,减5,抵消前面的加法
恢复时:
nums[0] = diff[0]
nums[1] = nums[0] + diff[1] = 0 + 5 = 5
nums[2] = nums[1] + diff[2] = 5 + 0 = 5
nums[3] = nums[2] + diff[3] = 5 + 0 = 5
nums[4] = nums[3] + diff[4] = 5 - 5 = 0
=> [0, 5, 5, 5, 0]
即区间 [1,3] 成功加了 5,其他区域不变!
🧱 模板代码(C++)
vector<int> getDiffArray(const vector<int>& nums)
{
int n = nums.size();
vector<int> diff(n);
diff[0] = nums[0];
for (int i = 1; i < n; ++i)
diff[i] = nums[i] - nums[i - 1];
return diff;
}
void rangeAdd(vector<int>& diff, int l, int r, int val)
{
diff[l] += val;
if (r + 1 < diff.size()) diff[r + 1] -= val;
}
vector<int> restoreArray(const vector<int>& diff)
{
int n = diff.size();
vector<int> res(n);
res[0] = diff[0];
for (int i = 1; i < n; ++i)
res[i] = res[i - 1] + diff[i];
return res;
}
🧪 示例题目
-
- 拼车
-
- 航班预订统计
-
- 区间加法
二维前缀和(2D Prefix Sum)
✅ 定义
对一个二维矩阵 matrix 构建前缀和 sum:
sum[i][j] 表示矩阵左上角 (0,0) 到 (i-1,j-1) 之间的子矩阵和
💡 构建公式:
sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j]
+ matrix[i][j];
✍️ 查询子矩阵和:
// 查询 (r1, c1) 到 (r2, c2) 的子矩阵和
int total = sum[r2 + 1][c2 + 1] - sum[r2 + 1][c1] - sum[r1][c2 + 1]
+ sum[r1][c1];
📘 公式详解
我们查询的区域是矩形 (r1, c1) 到 (r2, c2),如下图:
┌────────────┬──────────────┐
│ A │ B │
│ (0~r1-1, │ (0~r1-1, │
│ 0~c1-1) │ c1~c2) │
├────────────┼──────────────┤
│ C │ D │ ← 查询目标区域
│ (r1~r2, │ (r1~r2, │
│ 0~c1-1) │ c1~c2) │
└────────────┴──────────────┘
公式说明:
-
sum[r2+1][c2+1]: 包含整个大矩形 ABCD -
- sum[r2+1][c1]: 去除 AB 区域 -
- sum[r1][c2+1]: 去除 AC 区域 -
+ sum[r1][c1]: 加回重复减掉的 A 区域
🧱 模板代码
vector<vector<int>> compute2DPrefixSum(const vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> sum(m + 1, vector<int>(n + 1, 0));
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + matrix[i][j];
}
}
return sum;
}
🧪 示例题目
-
- 二维区域和检索 - 不可变
🎯 LeetCode 刷题 + C++ 代码
303. 区域和检索 - 数组不可变
class NumArray
{
vector<int> sum;
public:
NumArray(vector<int>& nums)
{
int n = nums.size();
sum.resize(n + 1, 0);
for (int i = 0; i < n; ++i) sum[i + 1] = sum[i] + nums[i];
}
int sumRange(int left, int right)
{
return sum[right + 1] - sum[left];
}
};
560. 和为 K 的子数组
如果我们有一个前缀和数组 sum,那么子数组 [i, j] 的和是:
sum[j + 1] - sum[i] == k
=> sum[j + 1] - k == sum[i]
也就是说,只要我们能在之前出现过 sum[i] == sum[j+1] - k,就找到一个子数组
int subarraySum(vector<int>& nums, int k)
{
unordered_map<int, int> prefixCount; // key是sum, value:表示sum出现的次数
prefixCount[0] = 1; // 初始化前缀和为 0 的次数为 1
int sum = 0, res = 0;
for (int num : nums)
{
sum += num; // 累加当前元素到前缀和
res += prefixCount[sum - k]; // 查找是否存在某个之前的前缀和为 sum - k,
++prefixCount[sum];
}
return res; // 子数组的个数
}
238. 除自身以外数组的乘积
(不使用前缀和,但属于“前后缀积”的技巧)
vector<int> productExceptSelf(vector<int>& nums)
{
int n = nums.size();
vector<int> res(n, 1);
int left = 1, right = 1;
for (int i = 1; i < n; ++i)
{
left *= nums[i - 1];
res[i] *= left;
}
for (int i = n - 2; i >= 0; --i)
{
right *= nums[i + 1];
res[i] *= right;
}
return res;
}
1094. 拼车(差分数组)
bool carPooling(vector<vector<int>>& trips, int capacity)
{
vector<int> diff(1001, 0);
for (auto& trip : trips)
{
int val = trip[0], from = trip[1], to = trip[2];
diff[from] += val;
if (to < 1001) diff[to] -= val;
}
int cur = 0;
for (int d : diff)
{
cur += d;
if (cur > capacity) return false;
}
return true;
}
304. 二维区域和检索 - 不可变
class NumMatrix
{
vector<vector<int>> sum;
public:
NumMatrix(vector<vector<int>>& matrix)
{
int m = matrix.size(), n = matrix[0].size();
sum = vector<vector<int>>(m + 1, vector<int>(n + 1, 0));
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + matrix[i][j];
}
}
}
int sumRegion(int r1, int c1, int r2, int c2)
{
return sum[r2 + 1][c2 + 1] - sum[r2 + 1][c1] - sum[r1][c2 + 1] + sum[r1][c1];
}
};

浙公网安备 33010602011771号