前缀和
一维
一个数组prefix
中,第i
个元素表示nums[0]
至nums[i-1]
的总和,那么我们就称这个prefix
数组是nums
数组的前缀和。
prefix
[
i
]
=
∑
j
=
0
i
nums
[
j
]
\text{prefix}[i] = \sum_{j=0}^{i} \text{nums}[j]
prefix[i]=j=0∑inums[j]
应用
1、快速计算下标为[i , j]区间的和。
prefix[j+1]
-prefix[i]
即为下标[i , j]之间元素的总和。
二维
class NumMatrix {
vector<vector<int>> sum;
public:
NumMatrix(vector<vector<int>> &matrix) {
int m = matrix.size(), n = matrix[0].size();
sum.resize(m + 1, vector<int>(n + 1));
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];
}
}
}
// 返回左上角在 (r1,c1) 右下角在 (r2,c2) 的子矩阵元素和
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];
}
};
作者:灵茶山艾府
链接:https://leetcode.cn/problems/range-sum-query-2d-immutable/solutions/2667331/tu-jie-yi-zhang-tu-miao-dong-er-wei-qian-84qp/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
差分
一维
所谓“差分”,是指原数组中每个元素与前一元素之差所形成的数组。
我们知道,对原数组进行诸位累加(前缀计算操作),所得到的数组为前缀和数组。差分数组,则是对其执行前缀计算后,能够得到原数组的那个数组 。
差分数组的主要作用,是帮助快速修改某段区间。
因此,当我们想要对原数组的 [l,r]
进行整体修改时,只需要对差分数组的l
和r+1
位置执行相应操作即可。
二维
扩展
1、前缀和与哈希表
力扣560.和为k的子数组
借助哈希表中判定重复元素
的功能,可以帮忙判断(当前的前缀和
-K
)是否出现在哈希表中,如果有那么久数量加一,如果没有就将当前前缀和
压入哈希表。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> haxi; // 用于存储前缀和出现次数
haxi[0] = 1; // 初始化,表示前缀和为0出现一次
vector<int> qian(nums.size() + 1, 0); // 前缀和数组
int ans = 0;
// 计算前缀和
for (int i = 1; i <= nums.size(); i++) {
qian[i] = nums[i - 1] + qian[i - 1];
}
// 查找满足条件的子数组
for (int i = 1; i <= nums.size(); i++) {
int complement = qian[i] - k;
if (haxi.find(complement) != haxi.end()) {
ans += haxi[complement]; // 增加满足条件的子数组个数
}
haxi[qian[i]]++; // 更新当前前缀和的出现次数
}
return ans;
}
};
或者也可以一次遍历即可。在遍历的同时判断(当前的前缀和
-K
)是否出现在哈希表中。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int ans = 0, s = 0;
unordered_map<int, int> cnt{{0, 1}}; // s[0]=0 单独统计
for (int x : nums) {
s += x;
// 注意不要直接 += cnt[s-k],如果 s-k 不存在,会插入 s-k
ans += cnt.contains(s - k) ? cnt[s - k] : 0;
cnt[s]++;
}
return ans;
}
};
作者:灵茶山艾府
链接:https://leetcode.cn/problems/subarray-sum-equals-k/solutions/2781031/qian-zhui-he-ha-xi-biao-cong-liang-ci-bi-4mwr/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2、差分数组的逆运用(前缀和)
差分数组的定义是每个位置的值表示原数组在该位置的累积变化量。
具体来说,如果你有一个差分数组 diff
,通过计算其前缀和得到的数组 prefix
,那么 prefix[i]
表示从开始到位置 i 的所有增量或减量的总和(变化量的总和)。
力扣1109.航班预定统计
class Solution {
public:
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
vector<int> temp(n+1, 0);
vector<int> ans(n, 0);
for(auto &x : bookings) {
int first = x[0];
int last = x[1];
int seats = x[2];
temp[first] += seats;
if (last + 1 <= n) { //注意这里的范围判断,最后一个元素的last+1溢出
temp[last + 1] -= seats;
}
}
int sum = 0;
for(int i = 1; i <= n; i++) {
sum += temp[i];
ans[i - 1] = sum; //加了多少个差分数组的和就是第多少个的航班的预定
}
return ans;
}
};
class Solution {
public:
string shiftingLetters(string s, vector<vector<int>>& shifts) {
int n = s.length();
vector<long long> shiftSum(n + 1, 0);
// 计算每个位置的累计移位
for (const auto& shift : shifts) {
int start = shift[0], end = shift[1];
int direction = shift[2] ? 1 : -1;
shiftSum[start] += direction;
shiftSum[end + 1] -= direction;
}
// 计算前缀和(变化量)
for (int i = 1; i <= n; ++i) {
shiftSum[i] += shiftSum[i-1];
}
// 应用移位到原字符串
for (int i = 0; i < n; ++i) {
int shift = ((shiftSum[i] % 26) + 26) % 26; // 确保结果为正
s[i] = ((s[i] - 'a' + shift) % 26) + 'a';
}
return s;
}
};
3、前缀和与二分(路径和)
二分查找:将数组进行排序后判断nums[mid]
与target
的大小。直到找到或者是遇到终点。
但是对于不同的闭开方式,终点有不同的说法:
- 左闭右闭:终点为
left<=right
- 左闭右开/左开右闭:终点为
left<right
- 左开右开:终点为
left+1<right
class Solution {
// lower_bound 返回最小的满足 nums[i] >= target 的 i
// 如果数组为空,或者所有数都 < target,则返回 nums.size()
// 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
// 闭区间写法
int lower_bound(vector<int> &nums, int target) {
int left = 0, right = (int) nums.size() - 1; // 闭区间 [left, right]
while (left <= right) { // 区间不为空
// 循环不变量:
// nums[left-1] < target
// nums[right+1] >= target
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1; // 范围缩小到 [mid+1, right]
} else {
right = mid - 1; // 范围缩小到 [left, mid-1]
}
}
return left;
}
// 左闭右开区间写法
int lower_bound2(vector<int> &nums, int target) {
int left = 0, right = nums.size(); // 左闭右开区间 [left, right)
while (left < right) { // 区间不为空
// 循环不变量:
// nums[left-1] < target
// nums[right] >= target
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1; // 范围缩小到 [mid+1, right)
} else {
right = mid; // 范围缩小到 [left, mid)
}
}
return left; // 返回 left 还是 right 都行,因为循环结束后 left == right
}
// 开区间写法
int lower_bound3(vector<int> &nums, int target) {
int left = -1, right = nums.size(); // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
// 循环不变量:
// nums[left] < target
// nums[right] >= target
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid; // 范围缩小到 (mid, right)
} else {
right = mid; // 范围缩小到 (left, mid)
}
// 也可以这样写
// (nums[mid] < target ? left : right) = mid;
}
return right;
}
public:
vector<int> searchRange(vector<int> &nums, int target) {
int start = lower_bound(nums, target); // 使用其中一种写法即可
if (start == nums.size() || nums[start] != target) {
return {-1, -1}; // nums 中没有 target
}
// 如果 start 存在,那么 end 必定存在
int end = lower_bound(nums, target + 1) - 1;
return {start, end};
}
};
作者:灵茶山艾府
链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/solutions/1980196/er-fen-cha-zhao-zong-shi-xie-bu-dui-yi-g-t9l9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
技巧:可以用面积之差来表示路径长度。
题解