前缀和+哈希

文章介绍了几种利用前缀和、哈希表和同余定理解决数组子数组问题的通用模板,如找到和为K的子数组、和可被K整除的子数组、和为奇数的子数组以及连续子数组和等。这些方法通过计算数组的前缀和,存储哈希表,并利用数学性质高效地找出符合条件的子数组。
摘要由CSDN通过智能技术生成

1.题目

以上这几题,解题模板都差不多

首先用一个数组保存前缀和

for(){

		根据题意,遍历前缀和,并进行运算后将其作为哈希的KEY

		如果哈希表已经存在该KEY则进行对应的操作

		如果哈希表未存在该KEY,将需要的VALUE存到对应KEY

}

2.解题代码

  1. 560. 和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数。

示例 1:

输入:nums = [1,1,1], k = 2
输出:2

示例 2:

输入:nums = [1,2,3], k = 3
输出:2
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int n = nums.size();
        int ans = 0;
        unordered_map<int,int>res;
        vector<int>pre(n+1);
        for(int i=0;i<n;i++){//求前缀和数组//前缀和第一个元素为0
            pre[i+1]=pre[i]+nums[i];
        }
        res[0]++;
        for(int i=0;i<n;i++){//遍历数组求前缀和,将nums各个元素的前缀和保存到下标1~n。
            int temp=pre[i+1]-k;//求当前该值减去k是否存在在哈希表中,如果存在,ans++;
            if(res.find(temp)!=res.end()){
                ans+=res[temp];
            }
                res[pre[i+1]]++; //将当前元素的前缀和出现次数放入哈希表
        }
        return ans;
        
    }
};
//遍历一次,求当前的nums元素对应的前缀和,然后减去k,如果存在于哈希表,表示当前元素与与之前某个元素之间的和即为k,即表示一个所求的子数组
/*
例如:假设S4-S2=K;
    S4=a1+a2+a3+a4;
    S2=a1+a2;
    循环遍历的时候,S2先保存在哈希表,等遍历到S4时候,发现S4-K存在于哈希表,即S4-S2=K
    那么表示a3+a4=K;
*/
  1. 974. 和可被 K 整除的子数组

给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的(连续、非空) 子数组 的数目。

子数组 是数组的 连续 部分。

示例 1:

输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

示例 2:

输入: nums = [5], k = 9
输出: 0
class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        unordered_map<int,int>res;
        int n = nums.size();
        vector<int>pre(n+1);
        for(int i=0;i<n;i++){//求前缀和数组//前缀和第一个元素为0
            pre[i+1]=pre[i]+nums[i];
        }
        int ans = 0;
        for(int i=0;i<=n;i++){
            int temp = (pre[i]%k+k)%k;//解答关键为同余定理//不同的语言负数取模的值不一定相同,有的语言为负数,对于这种情况需要特殊处理。
            if(res.find(temp)!=res.end()){
                ans+=res[temp];
            }
            res[temp]++;//记录该余数出现的次数
        }

        return ans;
    }
};
/*
    通过同余定理我们可以知道,两个数之差除以K为0的话,则两个数单独除以k的余数相等
    即如果(a-b)%K=0,则a%k==b%k;例如(16-3)%13==0,16%13=3,3%13=3;
    例如,假设(S4-S2)%K==0,那么我们可以知道存在一个子数组的和(a3+a4)%K==0
    根据同余定理可知
    S4%K==S2%K
    在一次循环遍历的过程中可以知道S2%K的结果先被存入哈希表,之后遍历到S4%K,发现前面哈希表存在,那么则表示存在子数组的和刚好整除K

*/
  1. 1524. 和为奇数的子数组数目

给你一个整数数组 arr 。请你返回和为 奇数 的子数组数目。

由于答案可能会很大,请你将结果对 10^9 + 7 取余后返回。

示例 1:

输入:arr = [1,3,5]
输出:4
解释:所有的子数组为 [[1],[1,3],[1,3,5],[3],[3,5],[5]] 。
所有子数组的和为 [1,4,9,3,8,5].
奇数和包括 [1,9,3,5] ,所以答案为 4 。

示例 2 :

输入:arr = [2,4,6]
输出:0
解释:所有子数组为 [[2],[2,4],[2,4,6],[4],[4,6],[6]] 。
所有子数组和为 [2,6,12,4,10,6] 。
所有子数组和都是偶数,所以答案为 0 。
class Solution {
public:
    int numOfSubarrays(vector<int>& arr) {
        unordered_map<int,int>res;
        int n = arr.size();
        int k = 2;
        vector<int>pre(n+1);
        for(int i=0;i<n;i++){//求前缀和数组//前缀和第一个元素为0
            pre[i+1]=pre[i]+arr[i];
        }
        res[0]++;
        long long ans = 0;
        for(int i=1;i<n+1;i++){
            int temp = ((pre[i]+1)%k+k)%k;
            if(res.find(temp)!=res.end()){//判断哈希表中存在相同的余数
                ans+=res[temp];
                ans%=(1000000007);
            }
            temp = (pre[i]%k+k)%k;
            res[temp]++;
        }
        return ans%1000000007;
    }
};
/*
   这一题通过上一题稍微转变一下思路即可。由上一题(974. 和可被 K 整除的子数组)通过同余定理解答我们可以知道,两个数之差除以K为0的话,则两个数单独除以k的余数相等
   这题我们要求子数组的和为奇数的个数,即求(a-b)%2==1,就是[(a+1)-b]%2==0
   由同余定理可得(a+1)%2==b%2,所以根据上一题的解题思路我们就可以求出答案了。
*/

4.523. 连续的子数组和

难度中等486

给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:

  • 子数组大小 至少为 2 ,且
  • 子数组元素总和为 k 的倍数。

如果存在,返回 true ;否则,返回 false

如果存在一个整数 n ,令整数 x 符合 x = n * k ,则称 xk 的一个倍数。0 始终视为 k 的一个倍数。

示例 1:

输入:nums = [23,2,4,6,7], k = 6
输出:true
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6 。

示例 2:

输入:nums = [23,2,6,4,7], k = 6
输出:true
解释:[23, 2, 6, 4, 7] 是大小为 5 的子数组,并且和为 42 。 
42 是 6 的倍数,因为 42 = 7 * 6 且 7 是一个整数。
class Solution {
public:
    bool checkSubarraySum(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int>pre(n+1);
        unordered_map<int,int>res;
        int ans;
        for(int i=0;i<n;i++){//求前缀和数组//前缀和第一个元素为0
            pre[i+1]=pre[i]+nums[i];
        }

        
        for(int i=0;i<=n;i++){
            int temp = pre[i]%k;//解答关键同余定理
            if(res.find(temp)!=res.end()){//如果哈希表中存在相同的余数
                ans = i-res[temp];//则用当前下标减去哈希表中对应余数的下标值,如果大于等于2则返回true
                if(ans >= 2){
                    return true;
                }
            }else{
                res[temp]=i;
            }
        }

        return false;
    }
};
/*
    很明显这题又是[974. 和可被 K 整除的子数组]的一种思路变换
    然后我们看一下这题就可知,由于不用统计子数组和为k的次数,所以我们的哈希表用来存对应的下标值,在一次循环遍历的过程中可以知道S2%K的结果先被存入哈希表,之后遍历到S4%K,发现前面哈希表存在,那么则表示存在子数组的和刚好整除K,此时我们用当前下标减去哈希表中对应余数的下标值,如果大于等于2则返回true
*/
  1. 1590. 使数组和能被 P 整除

难度中等70

给你一个正整数数组 nums,请你移除 最短 子数组(可以为 ),使得剩余元素的 能被 p 整除。 不允许 将整个数组都移除。

请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1

子数组 定义为原数组中连续的一组元素。

示例 1:

输入:nums = [3,1,4,2], p = 6
输出:1
解释:nums 中元素和为 10,不能被 p 整除。我们可以移除子数组 [4] ,剩余元素的和为 6 。

示例 2:

输入:nums = [6,3,5,2], p = 9
输出:2
解释:我们无法移除任何一个元素使得和被 9 整除,最优方案是移除子数组 [5,2] ,剩余元素为 [6,3],和为 9 。

示例 3:

输入:nums = [1,2,3], p = 3
输出:0
解释:和恰好为 6 ,已经能被 3 整除了。所以我们不需要移除任何元素。
class Solution {
public:
    int minSubarray(vector<int>& nums, int p) {
        unordered_map<int,int>res;
        res[0]=-1;
        int ans = 100005;
        long long sum = 0;
        long long k=0;
        int n = nums.size();
        for(int i=0;i<n;i++){
            k+=nums[i]%p;//模运算,(a+b)%p==(a%p+b%p)%p;
        }
        k%=p;//为求余后的余数
        if(!k)return 0;//如果为0,表示本身数组和为p的倍数无需删除
        for(int i=0;i<n;i++){
            sum+=nums[i];
            int temp = ((sum-k)%p+p)%p;//判断减去余数后的值是否在哈希表中存在
            if(res.find(temp)!=res.end()){//如果存在则取出哈希表中对应值的坐标,求当前下标与之差和暂存答案的最小值
                ans = min(ans,i-res[temp]);
            }
            temp = sum%p;
            res[temp]=i;//保存坐标
        }

        return ans>=n?-1:ans;
    }
};

/*
    这题依然类似[974. 和可被 K 整除]的子数组的一种思路转换。
    首先,我们通过模运算求出数组之和模p的余数k,之后我们变将题目转变为求子数组中和为k的问题,便是题目[560. 和为 K 的子数组]的思路,我们只要删除这一段子数组,那么总的数组和便是P的倍数,但竟然是求最小值,我们便可以像[523. 连续的子数组和]一样,哈希表存入对应的坐标,遍历判断减去余数后的值是否在哈希表中存在,如果存在就求当前下标与之差并且更新暂存答案ans的最小值
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值