前缀和和差分都是将O(n)操作优化为O(1)的算法。这两个算法都是用于对数组连续子数组的操作,前缀和用于对子数组求和,差分用于对子数组进行统一的加。一维的前缀和,差分是比较容易理解的,而二维的最好是辅助画图帮助理解。差分和前缀和是存在联系的,差分还原就是前缀和。
注意:当在画图理解二维前缀和时,要注意数组元素是代表线还是格子,不然容易得到错误的结论(如果是线的话,可能会得到s[x2][y2]-s[x1][y2]-s[x2][y1]+s[x1][y1],但实际上s[x1][y2],s[x2][y1]是包含了我们要求的矩阵的边界部分,这样并不对,而应该是s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1])
模板:sum[0]=a[0],sum[i]=sum[i-1]+a[i]
注意有些题是看起来能用滑动窗口的,但要注意滑动窗口必须满足单调
力扣53
一维前缀和有时候可以优化为空间O(1)(这个题还可以用一维dp解决)
class Solution {
public:
int maxSubArray(vector& nums) {
int ans=-1e6,minpre=0,pre=0;
for(int i=0;i
力扣1749
子数组的和等于前缀和的差,最大子数组和等于前缀和的最大值减去最小值
(可能存在疑问,万一最大值在前最小值在后是不是不符合?这里是绝对值,并不存在这样的问题)
class Solution {
public:
int maxAbsoluteSum(vector& nums) {
int n=nums.size();
int mmax=0,mmin=0;
int sum=0;
for(int i=0;i
力扣525
class Solution {
public:
int findMaxLength(vector& nums) {
//hashmap加前缀和。当和为0时,0和1的数目相等。
//如果i到j的0和1的数目相等那么i到j的前缀和相等
int sum=0,ans=0;
int n=nums.size();
mapm;
m[0]=-1;
for(int i=0;i
力扣560
class Solution {
public:
int subarraySum(vector& nums, int k) {
//思路:刚开始以为是用滑动窗口,但是存在负数,所以用前缀和。题目是和为k,我们可以转换为i之前存在多少sum[i]-k即可。(sum[j]-sum[i]=k,移项就得到了上述思路)
//注意:这里的子数组是指连续子数组
mapm;
m[0]=1;
int sum=0,ans=0;
int n=nums.size();
for(int i=0;i
模板:s[i][j] = s[i - 1][j] + s[i][j - 1 ] + a[i] [j] - s[i - 1][j - 1]
s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
注意边界
模板题 力扣1314
#include
#include
using namespace std;
class Solution {
public:
vector> matrixBlockSum(vector>& mat, int k) {
int n = mat.size();
int m = mat[0].size();
// 计算二维前缀和数组
vector> sum(n + 1, vector(m + 1, 0));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] + mat[i - 1][j - 1] - sum[i - 1][j - 1];
}
}
// 计算每个元素的区域和
vector> ans(n, vector(m, 0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
int x1 = max(0, i - k) + 1;
int y1 = max(0, j - k) + 1;
int x2 = min(n, i + k + 1);
int y2 = min(m, j + k + 1);
ans[i][j] = sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
}
}
return ans;
}
};
力扣221
这个题可以用二维dp来做,二维dp的动态转移方程的迷惑点在于那个min,这个是确保新形成的正方形被其他正方形包含
二维前缀和的关键在于如何判断正方形全是1:前缀和等于面积
二维dp
class Solution {
public:
int maximalSquare(vector>& matrix) {
int ans=0;
if(matrix.size()==0)
return ans;
int m=matrix.size(),n=matrix[0].size();
vector>dp(m,vector(n));
for(int i=0;i
二维前缀和
class Solution {
public:
int maximalSquare(vector>& matrix) {
int n = matrix.size();
int m = matrix[0].size();
vector> sum(n+1, vector(m+1, 0));
for(int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
sum[i+1][j+1] = sum[i+1][j] + sum[i][j+1] + matrix[i][j] - '0' - sum[i][j];
}
}
int ret = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
for (int k = 1; k <= min(i, j); ++k) {
int t = sum[i][j] - sum[i-k][j] - sum[i][j-k] + sum[i-k][j-k];
if (t == k * k) ret = max(ret, t);
}
}
}
return ret;
}
};
形成差分数组是右减左,还原数组是右加左
力扣 2848 模板题
class Solution {
public:
int numberOfPoints(vector>& nums) {
vectord(102);
int n=nums.size();
for(int i=0;ia(101);
int cnt=0,s=0;
for(int i=1;i<101;i++){
d[i]+=d[i-1];
if(d[i]>0)
cnt++;
}
return cnt;
}
};
力扣 1094
可能会有疑惑为什么这里对差分数组操作时不是to+1。
你细品,当我们思考to站的人时是不是并没有算上从from站上的人,所以实际上是[from,to-1]闭区间上加
class Solution {
public:
//思路:一开始想到的是对一段旅程之间的站点都加上乘客,但是好多O(n)大概会超时,一段上边加可以考虑差分
bool carPooling(vector>& trips, int capacity) {
vectord(1001);
int n=trips.size();
for(int i=0;icapacity)
return false;
}
return true;
}
};
模板:b[i][j] = a[i][j] − a[i − 1][j] − a[i][j − 1] + a[i −1 ][j − 1]
b[x1][y1] += c, b[x2 + 1][y1] -= c, b[x1][y2 + 1] -= c, b[x2 + 1][y2 + 1] += c;
注意边界
力扣2536 模板题
#include
using namespace std;
class Solution {
public:
vector> rangeAddQueries(int n, vector>& queries) {
vector> d(n + 1, vector(n + 1, 0);
for (int i = 0; i < queries.size(); i++) {
int x1 = queries[i][0], y1 = queries[i][1];
int x2 = queries[i][2], y2 = queries[i][3];
d[x1][y1] += 1;
d[x2 + 1][y1] -= 1;
d[x1][y2 + 1] -= 1;
d[x2 + 1][y2 + 1] += 1;
}
vector> ans(n, vector(n, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i > 0) d[i][j] += d[i - 1][j];
if (j > 0) d[i][j] += d[i][j - 1];
if (i > 0 && j > 0) d[i][j] -= d[i - 1][j - 1];
ans[i][j] = d[i][j];
}
}
return ans;
}
};
力扣2132
class Solution {
public:
// 如何保证不覆盖任何被占据的格子?确保区域内和为0,需要使用二维前缀和
// 如何标记被邮票覆盖的格子?二维差分。
bool possibleToStamp(vector>& grid, int stampHeight,int stampWidth) {
int m = grid.size(), n = grid[0].size();
vector> s(m + 1, vector(n + 1));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
s[i + 1][j + 1] = s[i + 1][j] + s[i][j + 1] - s[i][j] + grid[i][j];
}
}
vector>d(m+2,vector(n+2));
for(int i=stampHeight;i<=m;i++){
for(int j=stampWidth;j<=n;j++){
int i1=i-stampHeight+1;
int j1=j-stampWidth+1;
if(s[i][j]-s[i][j1-1]-s[i1-1][j]+s[i1-1][j1-1]==0){
d[i1][j1]++;
d[i1][j + 1]--;
d[i + 1][j1]--;
d[i + 1][j + 1]++;
}
}
}
for(int i=0;i
到这里前缀和和差分的基础学习就结束,这里只是结合题目描述了基础的应用,原理需要自己去画图理解,因为单纯去看别人给你讲述原理终究不如自己思考来得扎实。若是存在错误,希望大家多多包含并指出。