@bit::Shadow
✧(≖ ◡ ≖✿
目录
[第一步:预处理出一个前缀和数组 dp ](#第一步:预处理出一个前缀和数组 dp[ ])
[第二步:使用前缀和数组dp ](#第二步:使用前缀和数组dp[ ])
[4. 除了自身以外数组的乘积(normal)](#4. 除了自身以外数组的乘积(normal))
性质:利用空间换时间的思想,通过预处理+额外处理,快速得出数组中某连续区间值。
第一步:预处理一维/多维的前缀和/后缀和/乘积数组。
第二步:使用数组。
二维数组(x1,y1)~(x2,y2)区域面积和公式:
S = dpx2y2+dpx1-1y1-1 - dpx1-1y2 - dpx2y1-1;
图解:

1.前缀和(easy)
对于给定的长度为n的数组{a1,a2,··,an},我们有m次查询操作,每一次操作给出两个参
数l,r,你需要输出数组中第l到第r个元素之和。

输出描述:
对于每一次查询操作,在一行上输出一个整数,代表区间和。
示例1
输入:
3 2
1 2 4
1 2
2 3
输出:3
第一步:预处理出一个前缀和数组 dp
计算原理

第二步:使用前缀和数组dp
对应输出

cpp
#include <iostream>
using namespace std;
int main() {
int n,m;//n元素数量 m次查询
cin >> n >> m;
long long* dp = new long long[n+1]();//初始化为0
for(int i = 1;i <= n;i++)
{
cin >> dp[i];
if(i != 1)
dp[i] += dp[i - 1];
// cout << dp[i] << " ";
}
// cout << endl;
int l,r;
for(int i = 0;i < m;i++)
{
cin >> l >> r;
cout << dp[r] - dp[l-1] << endl;
}
}
2.二维前缀和(easy)
基于原数组arr的二维推导公式:

第一步:构造二维前缀和数组
cpp
dp[i][j] = dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+nums[i-1][j-1];
第二步:使用前缀和数组
求arr两坐标(x1,y1)与(x2,y2)间面积公式:
S = dpx2y2+dpx1-1y1-1 - dpx1-1y2 - dpx2y1-1;
注意D区域被减去两次。
永远:
x2 > x1
y2 > y1

cpp
#include <iostream>
#include<vector>
using namespace std;
int main() {
int line,col,q;
cin >> line >> col >> q;
// vector<vector<int>> dp((line+1)*(col+1),0);
vector<vector<long long>> dp(line+1,vector<long long>(col+1, 0));
//真正的内部分配0
// long long* dp = new long long[(line+1)*(col+1)]();//语法正确但是访问不能以[i][j]式访问
// long long* dp = new long long[line+1][col+1]();//语法错误
for(int i = 1;i <= line;i++)
{
for(int j = 1;j <= col;j++)
{
cin >> dp[i][j];
dp[i][j] += dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1];
// cout << dp[i][j] << " ";
}
// cout << endl;
}
//[i][j]处表示自1,1到i,j的和值
for(int i =0;i < q;i++)
{
int x1,y1;
int x2,y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << dp[x1-1][y1-1]+dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1] << endl;
}
}
// 64 位输出请用 printf("%lld")
3.寻找数组中心下标(easy)
思路:在原dp数组两侧添加'0'标记位作为判断结束返回"-1"的依据。
给你一个整数数组 nums ,请计算数组的 中心下标。
数组中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。

cpp
class Solution {
public:
int pivotIndex(vector<int>& nums)
{
int len = nums.size();
vector<int> dp(len+2,0);//len+2个
for(int i = 1;i <= len;i++)
{
dp[i] =dp[i-1] + nums[i-1];
}
for(int c = 1;c <= len;c++)
{
if(dp[c-1] == dp[len]-dp[c]) return c-1;
}
return -1;
}
};
4.除了自身以外数组的乘积(normal)
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除了 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 不要使用除法, 且在 O(n) 时间复杂度内完成此题。
输入: nums = [1,2,3,4]输出: [24,12,8,6]
cpp
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> fdp(n+1,1);
vector<int> bdp(n+1,1);
vector<int> ret(n,1);
for(int i = 1;i <= n;i++)
{
fdp[i] = fdp[i-1]*nums[i-1];
bdp[i] = bdp[i-1]*nums[n-i];
// cout << fdp[i] << " " << bdp[i] << endl;
}
for(int i =0;i<n;i++)
{
// if(i == 0)
// {
// ret[i] = bdp[n-1];
// continue;
// }
// 对应错误!原因:fdp中前置1与第一个真值"1"搞混了草稿打错
// 再者:既然bdp都没有越界 fdp怎么会越界额外判断呢?
// ret[i] = fdp[i-1]*bdp[n-i-1];
ret[i] = fdp[i]*bdp[n-i-1];
}
return ret;
}
};
5.找和为K的子数组(normal)
err
这里竟然使用哈希表来辅助解决负数出现的顺序问题
1.边统计边验证是否符合sum-k值
2.count+=sum-k//而不是++ 代表同时计入旧子数组数目
3.unordered_map<int, int>中find库函数的使用
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k的子数组的个数。
子数组是数组中元素的连续非空序列。
示例 1:
输入:nums = [1,1,1], k = 2
输出:2
示例 2:
输入:nums = [1,2,3], k = 3
输出:2
提示:
1 <= nums.length <= 2 * 104-1000 <= nums[i] <= 1000-107 <= k <= 107
cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> hash;
hash[0]=1;
int sum = 0, count=0;
for(auto i:nums)
{
sum += i;
if(hash.find(sum - k) != hash.end()) count+=hash[sum-k];
hash[sum]++;
}
return count;
}
};
6.和为n的被K整除的子数组(normal)
本题不依赖课纯手搓1.6h
前缀合:
思路一:手搓(难死了呜呜呜😭)
fdp结合哈希表<int, int> 词典
0.边纳入边+=补余数集。
1.查找+=首余数集。
2.二次+=副余数集。
cpp
#include<stdio.h>
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
vector<int> fdp(nums.size()+1,0);
// unordered_map<int, int> _hash;
int count = 0;
for(int i =1;i <= nums.size();i++)
{
fdp[i] = fdp[i-1] + nums[i-1];
// cout << fdp[i] << " ";
}
unordered_map<int, int> hash;
hash[0]++;
for(int i = 1;i <= nums.size();i++)
{
hash[fdp[i]%k]++;
count+=hash[fdp[i]%k]-1;//减1处理刚累加值
// 取n%7的两个正负余数
int t = fdp[i] % k > 0 ? fdp[i] % k - k : fdp[i] % k + k;
if (hash[t] >= 1)
count += hash[t];
}
return count;
}
};
思路二:
优先解决"余数"问题:负%正==负 每个数(除0)都有两个数可以补充其无法整除的效果(像6%5=1 4、-1都可以补充)
①同余定理:( a ± b) % k == 0 ---> a%k == b%k
②正负余数统一化:

cpp
class Solution {
public:
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int,int> hash;
vector<int> v(nums.size()+1,0);
hash[0]++;
for(int i = 1;i <= nums.size();i++)
v[i] = v[i-1]+nums[i-1];
//
int cnt = 0;
for(int i = 1;i <= nums.size();i++)
{
hash[(v[i]%k+k)%k]++;//负数修正--统一化
if(hash[(v[i]%k+k)%k] != 1)
cnt+=hash[(v[i]%k+k)%k]-1;
}
return cnt;
}
};
反思:
你应该先想到余数问题的两种情况!!
然后才能优化逻辑。
怎么想到余数问题的两种情况?解决这类问题正是"技术能力""优化能力"的关键特别是对于我的学习领域。
广泛适当跳脱的思考,数字的敏感。
7.连续最长和为0子数组(normal)
err但是:思路+手动实现
给定一维数组,元素为1或0。找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
1.问题转换:凭借数字的敏感,与数据复杂度。将0记作-1,1记作1。那么题意就是要求求最长的和为0的子数组。
2.前缀和+哈希表。 前缀和求和,哈希表存储键值。

哈希表 < 前缀和, 下标 >
3.什么时候将键值对存储在哈希表内?
使用完后。也可以使用前不过要报对应下标提前存储。
cpp
class Solution {
public:
int findMaxLength(vector<int>& nums) {
vector<int> dp(nums.size()+1,0);
for(int i = 1;i <= nums.size();i++)
{
if(nums[i-1] == 0)
dp[i] = -1+dp[i-1];
else
dp[i] = nums[i-1]+dp[i-1];
printf("dp[%d]:%d \n",i,dp[i]);
}
int ret = 0;
unordered_map<int,int> hash;
vector<int> v(nums.size()+1,0);//存储长度v[5]=5-1
hash[0] = 0;
for(int i = 1;i <= nums.size();i++)
{
//***************************************
//先使用:即查找目标值
if(hash.find(dp[i]) != hash.end())
{
// ***长度存储***
v[i] = i - hash[dp[i]];
printf("v[%d]:%d ",i,v[i]);
ret = v[i] > ret ? v[i]:ret;
}
//***************************************
//使用完后纳入哈希表 <前缀和,下标>
//if(hash[dp[i]] == 0)//err与hash[0]矛盾
if(hash.find(dp[i]) == hash.end())//若dp[i]先前已经存储了,重复不纳入
hash[dp[i]] = i;
}
return ret;
}
};
哈希表存储什么?// err
关键在存储什么吗?关键在于怎样建立映射关系 其中包含对应下标出其长度值其遍历前位置
8.矩阵区域和(normal)
非常经典的二维区域定值处理中等题。
一丁丁点提示+手搓。
解题过程概述:逻辑抽象(35)+代码实现(15)+调试(50)。
难点:
元素块溢出 处理
待剪块定位处理
_r与_c的映射错误而且是两次错误,原因:
1.逻辑性错误,以c来决定_c边界。
2.两张表下标照应处理混乱。
给你一个 m x n 的矩阵 mat 和一个整数 k ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和:
i - k <= r <= i + k,j - k <= c <= j + k且(r, c)在矩阵内。
例如

题意理解抽象以行列双循环,自0,0遍历所有元素每个元素作为正方形(边长: 2*k+1)的中心点。
超出则截断,仅保留有效元素。
求各个元素对应的方形内所有元素的和值,并将此和值赋到返回的二维数组的对应位置。
例:k==1
mat22处,对应方形如图青色元素值,全都交给返回对象的ret22处。

因此我们要使用二维前缀和的方法来解决此问题:
二维和值索引需上左额外加一(000)层,以方便求值。

当k=1方形区边长为3,以1,1为例:

代码实现
cpp
class Solution {
public:
vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
// **********
//逻辑原理--->代码实现 而非 数值规律--->代码实现
// **********
//二维矩阵和
vector<vector<int>> vv(mat.size()+1, vector(mat[0].size()+1, 0));
for(int i = 1;i <= mat.size();i++)
{
for(int j = 1;j <= mat[0].size();j++)
{
vv[i][j] = vv[i-1][j]+vv[i][j-1]-vv[i-1][j-1]+mat[i-1][j-1];
printf("%d ",vv[i][j]);
}
}
//
int row = vv.size();//行数5
int col = vv[0].size();//列数4
vector<vector<int>> ret(row-1,vector<int>(col-1,0));
for(int i = 0;i < row-1;i++)//row-1减去多加的一行 循环次数由真矩阵行决定
{
int r = k+1+i > row-1? row-1 : k+1+i;//溢出保留最大行
//_r定位减去值
int _r = i > k? i-k:0;
for(int j = 0;j < col-1;j++)//row-1减去多加的一列 循环次数由真矩阵列决定
{
int c = k+1+j > col-1? col-1 : k+1+j;//溢出保留最大列
int _c = j > k? j-k:0;
// printf("%d%d\n",r,c);//重要⚠️验证行列的定位
// printf("%d%d %d %d %d %d ",r,c,vv[r][c],vv[_r][_c],vv[r][_c],vv[_r][c]);
ret[i][j] = vv[r][c]+vv[_r][_c]-vv[r][_c]-vv[_r][c];
}
}
return ret;
}
};
感谢支持,持续更新
欢迎关注
