算法入门:专题前缀和:一二维前缀和 寻找数组的中心下标 除自身以外数组的乘积 和为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 =fi-1 + numsi-1;

gi表示i+1,n-1区间,所有元素的和。gi =gi+1+numsi+1.

然后去判断是否fi== gi.

处理细节: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 =fi-1 * numsi-1;

gi表示i+1,n-1区间,所有元素的积。gi =gi+1 * numsi+1.

处理细节问题,如果前缀积的第一个和后缀积的最后一个为0,结果就会发生变化,所以f0 = 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 → hash0=1 → 找到1个子数组 1,2(这一步也就解释了为什么需要hash0=1)
  3. num=3: sum=6, sum-k=3 → hash3=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/)()

cpp 复制代码
class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        unordered_map<int ,int>hash;
        hash[0] = -1;//默认有一个前缀和为0的情况
        int ret = 0,sum = 0;//sum标记当前位置之前的一个前缀和
        for(int i = 0;i<nums.size();i++)
        {
            if(nums[i] == 0)nums[i] = -1;
            sum += nums[i];
            if(hash.count(sum))ret = max(ret,i-hash[sum]) ;//找到hash<sum,int>里面存的就是下标
            else hash[sum] = i;//如果前面一段没有和为sum的就存入
            //这两行的逻辑:如果找到的sum = 0 ,之前出现过,就更新为最长的,没出现过就写入当前的下标,此时这个下标对应的sum = 0
        }
        return ret;
    }
};

矩阵区域和

[1314. 矩阵区域和 - 力扣(LeetCode)](https://leetcode.cn/problems/matrix-block-sum/description/)()

cpp 复制代码
class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
        // 预处理一个前缀和矩阵
        int m = mat.size();
        int n = mat[0].size();

        // 初始化一个dp的矩阵
        vector<vector<int>> dp(m + 1,
                               vector<int>(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<vector<int>> ret(m, vector<int>(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;
    }
};
相关推荐
江畔柳前堤3 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore4 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考5 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX6 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面7 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号7 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽7 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI7 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者9 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者9 小时前
【征程6】校准量化中HistogramObserver解析
算法