算法入门:专题前缀和:一二维前缀和 寻找数组的中心下标 除自身以外数组的乘积 和为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;
    }
};
相关推荐
8Qi84 小时前
LeetCode 75:颜色分类(荷兰国旗问题)—— Java 题解 ✅
java·算法·leetcode·指针·排序
888CC++6 小时前
如何在 C 语言中进行程序调试?
前端·javascript·算法
(●—●)橘子……7 小时前
力扣第503场周赛练习理解
python·学习·算法·leetcode·职场和发展·周赛
明志数科9 小时前
4D时序标注技术详解:让机器人理解连续动作的数据基础
java·算法·机器人
KaMeidebaby9 小时前
卡梅德生物技术快报|原核表达系统工艺优化:包涵体重折叠 + 分子筛纯化实现功能 RBD 高效制备,附全参数配置
前端·人工智能·算法·数据挖掘·数据分析
无限码力9 小时前
携程0510笔试真题【单数组交换】
算法·携程笔试·携程笔试真题·携程0510笔试真题
BlockWay10 小时前
WEEX Labs 周度观察:微软-OpenAI 合作调整与AI 多云趋势
大数据·人工智能·算法·安全·microsoft
风筝在晴天搁浅10 小时前
快手 CodeTop LeetCode 224.基本计算器
数据结构·算法·leetcode
Smoothcloud润云11 小时前
5大功能精修,重构AI算力使用体验!
java·人工智能·windows·算法·重构·编辑器·sublime text