算法入门:专题前缀和:一二维前缀和 寻找数组的中心下标 除自身以外数组的乘积 和为k的子数组 和可被k整除的子数组 连续数组 矩阵区域和

🎬 胖咕噜的稞达鸭个人主页
🔥 个人专栏 : 《数据结构《C++初阶高阶》
《Linux系统学习》
《算法入门》

⛺️技术的杠杆,撬动整个世界!


一维前缀和

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
    int n, q;//n是数组元素的个数,q表示第几个
    cin >> n >> q;
    //1.申请一个数组
    vector<int>arr(n + 1);//多申请一个元素,因为下标要从1开始
    for (int i = 1; i <= n; i++)cin >> arr[i];
    //2.形成一个前缀和数组
    vector<long long> dp(n + 1); //long long 处理边界情况
    for (int i = 1; i <= n; i++)dp[i] = dp[i - 1] + arr[i];
    //3.使用前缀和数组来计算任意一段区间的和
    int l = 0, r = 0;
    while (q--) {
        cin >> l >> r;
        cout << dp[r] - dp[l - 1] << endl;//如果要计算dp[2]-dp[0];再用这个公式,算出来就是dp[2]-dp[-1],我们假定dp[0]=0;
    }
    return 0;
}

二维数组二维前缀和

https://www.nowcoder.com/practice/99eb8040d116414ea3296467ce81cbbc?tpId=230&tqId=2023819&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196

题目描述:

要求把[i,j]位置的元素计算出来,也即是二维数组,计算1到i,1到j所有下标的数值,可以类似于计算面积;看图更清楚

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
//dp[i][j]表示从【1,1】的位置到【i,j】位置,这段区间里面所有元素的和
//dp[i][j]=dp[i-1]dp[j]+dp[j-1][i]+arr[i][j]-dp[i-1][j-1]
int main() {
    //1.读入数据
    int n=0,m=0,q=0;
    cin >> n >> m >> q;
    vector<vector<int>> arr(n+1,vector<int>(m+1));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
           cin >> arr[i][j];
    //2.预处理前缀和矩阵
    vector<vector<long long>>dp(n+1,vector<long long>(m+1))
    for(int i = 1;i<=n;i++)
        for(int j=1;j<=m;j++)
            dp[i][j]=dp[i-1][j]+dp[i][j-1]+arr[i][j]-dp[i-1][j-1];
    //3.使用前缀和数组
    int x1=0,x2=0,y1=0,y2=0;
    while(q--)
    {
        cin>>x1>>y1>>x2>>y2;
        cout<<dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1]<<endl;
    }
    return 0;
}
// 64 位输出请用 printf("%lld")

寻找数组的中心下标

解法:用前缀和

用f来表示前缀和数组,g来表示后缀和数组。

f[ i]表示[0,i-1]区间,所有元素的和;f [ i ]=f[i-1] + nums[i-1];

g[i]表示[i+1,n-1]区间,所有元素的和。g[i ]=g[i+1]+nums[i+1].

然后去判断是否f[i]== g[i].

处理细节:f(0)=0;g(n-1)=0;

cpp 复制代码
class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        int n =nums.size();
        vector<int>f(n),g(n);

        //预处理前缀和数组和后缀和数组
        for(int i=1;i<n;i++)
            f[i]=f[i-1]+nums[i-1];
        for(int i = n-2;i>=0;i--)
            g[i]=g[i+1]+nums[i+1];

        //使用
        for(int i =0;i<n;i++)
            if(f[i] == g[i])
            return i;
        return -1;
    }
};

除自身以外数组的乘积

https://leetcode.cn/problems/product-of-array-except-self/description/

题解:返回除了这个数字之外的前面一段数组和后面一段数组的积。

解法:前缀积和后缀积

用f来表示前缀积数组,g来表示后缀积数组。

f[ i]表示[0,i-1]区间,所有元素的积;f [ i ]=f[i-1] * nums[i-1];

g[i]表示[i+1,n-1]区间,所有元素的积。g[i ]=g[i+1] * nums[i+1].

处理细节问题,如果前缀积的第一个和后缀积的最后一个为0,结果就会发生变化,所以f[0 ] = g[ n-1] =1

cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        //计算nums的长度
        int n = nums.size();
        vector<int> f(n),g(n);
        //处理细节问题,这道题涉及到乘积问题,所以前缀和的第一个数字和后缀和的最后一个数字一定是等于1,不然累积下来结果不对
        f[0] = g[n-1] = 1;
        for(int i = 1;i<n;i++)
            f[i] = f[i-1] * nums[i-1];
        for(int i = n-2;i>=0;i--)
            g[i] = g[i+1] * nums[i+1];
        //定义一个数组返回最后的值
        vector<int>ret(n);
        for(int i = 0;i< n;i++)
        {
            ret[i]=f[i] * g[i];
        }
        return ret;
    }
};

和为k的子数组

https://leetcode.cn/problems/subarray-sum-equals-k/description/

题解:本题涉及到数组中元素的个数及每一个元素,建议使用哈希表+前缀和的方式求解;

题目要求求解的是和为k的子数组,那么我们可以倒着加,假设最后一个元素为i,那就需要在【0,i-1】中寻找和为k的元素数组,定义一个sum ,用来统计遍历到的元素的和,
sum - k:如果存在某个之前的前缀和等于 sum - k,那么:

从那个位置到当前位置的子数组和 = sum - (sum - k) = k

举个例子:

数组: [1, 2, 3], k = 3

遍历过程:

  1. num=1: sum=1, sum-k=-2 → 不存在,不累加
  2. num=2: sum=3, sum-k=0 → hash[0]=1 → 找到1个子数组 [1,2](这一步也就解释了为什么需要hash[0]=1)
  3. num=3: sum=6, sum-k=3 → hash[3]=1 → 找到1个子数组 [3]
    随后,更新哈希表,将当前前缀和 sum 在哈希表中的计数加1,如果 sum 不存在,hash[sum] 会初始化为0然后加1。
cpp 复制代码
class Solution {
public:
 int subarraySum(vector<int>& nums, int k) {
        unordered_map<int ,int>hash;//统计前缀和出现的次数
 hash[0]=1;//hash表中0出现了一次
 int sum = 0,ret  = 0;
 for(auto x : nums)
        {
            sum += x;//计算当前位置的前缀和
			if(hash.count(sum - k))ret += hash[sum-k];//检查hash.count()是否存在
			hash[sum]++;//将当前前缀和 sum 在哈希表中的计数加1
                        //如果 sum 不存在,hash[sum] 会初始化为0然后加1
        }
		return ret;
    }
};

和可被k整除的子数组

https://leetcode.cn/problems/subarray-sums-divisible-by-k/description/

同余定理:

复制代码
(a-b)/p = k......0
可以说明
 |
 |
 |
\ /
a % p = b % p 

举个例子:

复制代码
(26-12)/ 7 = 2
(12 % 7)= (26 % 7) = 5
其实本质也就是取余数,(12 + 2*7)/7 = 12 % 7;
也就是(a + p * k) % p = a % p;

2.C++,java中【负数 % 正数】的结果以及修正

复制代码
负数 % 正数 = 负数 ---> a % p + p

所以在正负数中统一取模:

复制代码
 (a % p + p )% p

前缀和+哈希表

设想有一个i,从【0,i】中有一个前缀和x,

复制代码
(sum - x) % k = 0;//在[0,i]中有一段区间能够被k整除
 |
 |
 |
\ /
sum % k = x % k;///在[0,i-1]区间内,找到有多少个前缀和的余数等于(sum % k + k) % k

用一个哈希表<int,int>(前缀和的余数,次数)

cpp 复制代码
class Solution { 
public: 
int subarraysDivByK(vector<int>& nums, int k) 
{ 
	unordered_map<int,int>hash; hash[0 % k]=1;//0这个数的余数
	 int sum = 0,ret = 0 ;
	 for(auto x : nums) 
	{ 
		 sum += x; 
		 int r = (sum % k + k) % k;//修正之后的余数 
		 if(hash.count(r))ret += hash[r];//统计结果 
		 hash[r]++; 
	} 
		return ret; 
	} 
};

连续数组

[525. 连续数组 - 力扣(LeetCode)](https://leetcode.cn/problems/contiguous-array/description/)\]() ![!\[\[2 5.png\]\]](https://i-blog.csdnimg.cn/direct/19b31ae7e63f409c8d676cefbd091600.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/be15a46f609e4b2bb5a1a9b6eadca206.jpeg) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/4d2a1bcf9f7947cf95d8c7ae13e8d752.jpeg) ```cpp class Solution { public: int findMaxLength(vector& nums) { unordered_maphash; hash[0] = -1;//默认有一个前缀和为0的情况 int ret = 0,sum = 0;//sum标记当前位置之前的一个前缀和 for(int i = 0;i里面存的就是下标 else hash[sum] = i;//如果前面一段没有和为sum的就存入 //这两行的逻辑:如果找到的sum = 0 ,之前出现过,就更新为最长的,没出现过就写入当前的下标,此时这个下标对应的sum = 0 } return ret; } }; ``` ### 矩阵区域和 \[[1314. 矩阵区域和 - 力扣(LeetCode)](https://leetcode.cn/problems/matrix-block-sum/description/)\]() ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/27a4a1e614b24a26a59f74f4c1b3058a.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/befb5dee8adf4b81ac5b1d68473dad08.jpeg) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/97b84d3afffc4cd7a2352be498f5fe49.jpeg) ```cpp class Solution { public: vector> matrixBlockSum(vector>& mat, int k) { // 预处理一个前缀和矩阵 int m = mat.size(); int n = mat[0].size(); // 初始化一个dp的矩阵 vector> dp(m + 1, vector(n + 1)); // 多加一行方便处理边界情况 for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + mat[i - 1][j - 1]; // 处理下标的映射关系 // 使用 vector> ret(m, vector(n)); for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) { int x1 = max(0, i - k) + 1, y1 = max(0, j - k) + 1; int x2 = min(m - 1, i + k) + 1, y2 = min(n - 1, j + k) + 1; ret[i][j] = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1]; } return ret; } }; ``` ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e9f8861982594025ad05e4e9d53ed3c7.jpeg)

相关推荐
天赐学c语言5 小时前
12.13 - 岛屿数量 && C语言中extern关键字的作用
c++·算法·leetcode
AndrewHZ5 小时前
【图像处理基石】如何入门图像金字塔算法技术?
图像处理·算法·计算机视觉·cv·拉普拉斯变换·图像金字塔
_w_z_j_5 小时前
全排列问题(包含重复数字与不可包含重复数字)
数据结构·算法·leetcode
7ioik5 小时前
jvm垃圾回收算法?
jvm·算法
@小码农5 小时前
LMCC大模型认证 青少年组 第一轮模拟样题
数据结构·人工智能·算法·蓝桥杯
dragoooon345 小时前
[hot100 NO.13~18]
算法
WangLanguager5 小时前
Prototypical Networks 在图像识别中表现如何?
算法
我是你们的明哥5 小时前
A*(A-Star)算法详解:智能路径规划的核心技术
后端·算法
我是你们的明哥5 小时前
从 N 个商品中找出总价最小的 K 个方案
后端·算法