前缀和 前缀积(k倍区间)

题目一

1230. K倍区间

给定一个长度为$N$的数列,$a1,a2,a3,----an$ 

如果其中一段连续的子序列 $ai,ai+1,ai+2,----aj$ 之和是$K$的倍数,我们就称这个区间 $[i,j]$是$K$倍区间。

你能求出数列中总共有多少个$K$倍区间吗?

输入格式

第一行包含两个整数 $N$ 和$K$

以下$N$行每行包含一个整数 $Ai$ 

输出格式

输出一个整数,代表 $K$倍区间的数目。

数据范围

1N,K100000  ,
1100000 

输入样例:

5 2
1
2
3
4
5

输出样例:

6

首先看到这个题看到这数据范围是一脸懵逼的,第一感觉一定是超时的

这个题解写的很好可以看看传送门


这里用到了知识点就是(sum[r]-sum[l])%k==sum[r]%k-sum[l]%k
所以我们只需要维护一个sum[i]%k就行,
就是每一次我们:
ans+=cnt[sum[i]]; 
cnt[sum[i]]++;

这个可以求出最后的答案,一定要注意一开始要初始化cnt[0]=1,或者最后加上cnt[0]

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;
typedef long long ll;

int n, k, cnt[N], sum[N];
ll ans;

int main(){

    cnt[0] = 1;

    scanf("%d%d", &n, &k);

    for(int i = 1; i <= n; i++){
        scanf("%d", &sum[i]);
        sum[i] += sum[i - 1] % k; //数很大,要不断取模,事实上影响能否整除这一性质的只有余数,所以保存取模的结果即可
        ans += cnt[sum[i] % k]; //找前面有多少个j
        cnt[sum[i] % k]++; //对于当前这个i要计数
    }

    printf("%lld\n", ans);

    return 0;
}

题目二:

传送门

给定一个长度为$n$且不包含$0$的整数序列 $a,a,,an$


请你计算以下两值:


  1. 使得$al×al+1××ar$为负的索引对$(l,r)(lr)$的数量。
  2. 使得$al×al+1××ar$为正的索引对$(l,r)(lr)$的数量。

输入格


第一行一个整数 $n$ 。


第二行包含$n$个整数 $a,,an$ 。


输出格式


共一行,输出单个空格隔开的两个整数,分别表示负的索引对数和正的索引对数。


数据范围


1n2×1e5  
 −1e9≤ai≤1e9,ai≠0 。


输入样例1:


5
5 -3 3 -1 1

输出样例1:


8 7

输入样例2:


10
4 2 -4 3 1 2 -4 3 2 3

输出样例2:


28 27

输入样例3:


5
-1 -2 -3 -4 -5

输出样例3:


9 6
这个题可以用上一个前缀和的思想来解决这个题,就是不考虑大小只考虑正负的话
维护一个sum[i]为前i个数乘积的正负,如果大于0的话那么找一下前面的sum[j]的正数的个数,
就是这个a[i]为正数的贡献,我们可以用中的区间减去正的或者负的这一得到另一个,就是这样。
这个前缀乘积就是和上面的思想是差不多的
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

int n;

int main()
{
    scanf("%d", &n);
    LL rp = 0, rn = 0;
    int sp = 1, sn = 0, s = 1;

    while (n -- )
    {
        int a;
        scanf("%d", &a);
        if (a < 0) s *= -1;
        if (s > 0) rp += sp, rn += sn, sp ++ ;
        else rp += sn, rn += sp, sn ++ ;
    }

    printf("%lld %lld\n", rn, rp);
    return 0;
} 

题目三:

525. 连续数组

给定一个二进制数组, 找到含有相同数量的 0 和 1 的最长连续子数组(的长度)。

 

示例 1:

输入: [0,1]
输出: 2
说明: [0, 1] 是具有相同数量0和1的最长连续子数组。

示例 2:

输入: [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。

 

注意: 给定的二进制数组的长度不会超过50000。

这个题其实是可以把1看成1,把0看成-1的然后就转化为求区间内的的一段区间的前缀和为0的数组最长的长度这个就转化为和上面的题目差不多了
就是(sum[r]-sum[l-1]==0)  ====> sum[l-1]==sum[r]就行,还有根据贪心,如果它没有出现这个mp[sum]的话加入出现了的话就不要加入了,
因为这个是mp[sum]越靠前越好
class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        int n=nums.size();
        vector<int>v(n);
        unordered_map<int,int>mp;
        mp[0]=-1;
        int sum=0,ans=0;
        for(int i=0;i<n;i++){
            if(nums[i]==1){
                sum+=1;
            }
            else sum-=1;
            if(!mp.count(sum)){
                mp[sum]=i;
            }
            else{
                ans = max(ans,i-mp[sum]);
            }
        }
        return ans;
    }
};

 题目四:

560. 和为K的子数组

给定一个整数数组和一个整数k,你需要找到该数组中和为k的连续的子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
说明 :

数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。
这个题也是一样的,就是用mp存一个前缀和出现的次数然后每次求一下这个ans+=mp[sum-k];

然后就是需要注意一下这个mp[0]=1;其实还是前缀和的应用

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int,int>mp;
        int n=nums.size();
        mp[0]=1;
        int sum=0;
        int ans=0;
        for(int i=0;i<n;i++){
            sum+=nums[i];
            ans+=mp[sum-k];
            mp[sum]++;
        }
        return ans;
    }
};

 

题目五:

930. 和相同的二元子数组

给你一个二元数组$nums$,和一个整数$goal$,请你统计并有多少个和为$goal$的 非空 子数组。

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

 

示例 1:

输入:nums = [1,0,1,0,1], goal = 2
输出:4
解释:
如下面黑体所示,有 4 个满足题目要求的子数组:
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
示例 2:

输入:nums = [0,0,0,0,0], goal = 0
输出:15
 

提示:

1 <= nums.length <= 3 * 104
nums[i] 不是 0 就是 1
0 <= goal <= nums.length

这个题也是很简单的

 

class Solution {
public:
    int numSubarraysWithSum(vector<int>& nums, int S) {
        unordered_map<int,int> mp;
        mp[0] ++;
        int ans = 0, s = 0;
        for(auto x:A){
            s += x;
            ans += mp[s - S];
            mp[s] ++;
        }
        return ans;
    }
};

 题目六:

974. 和可被 K 整除的子数组


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

示例:


输入:A = [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]
 


提示:


1 <= A.length <= 30000
-10000 <= A[i] <= 10000
2 <= K <= 10000


这个题还是一个前缀和的题目sum[r]-sum[l])%k==sum[r]%k-sum[l]%k
然后你要(sum[r]-sum[l])%k==0,然后就是sum[r]%k==sum[l]%k的数量
然后对于每一个值i,都有count += m[sum];//取余之后的


 

 
class Solution {
public:
    int subarraysDivByK(vector<int>& A, int K) {
        unordered_map<int,int> m;//和,出现次数
        m[0] = 1;//0出现1次
        int count = 0, sum = 0;
        for(int i = 0; i < A.size(); ++i)
        {
            // sum += A[i]+10000*K;//+10000*K,防止负数
            sum += (A[i]%K+K)%K;//更专业的写法,效果同上
            sum %= K;//除余sum在 0-K-1之间
            count += m[sum];//前面 0 - K-1的状态有多少个,之间的连续和可被K整除
            m[sum]++;
        }
        return count;
    }
};

题目七:

. 使数组和能被 P 整除

给你一个正整数数组$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 整除了。所以我们不需要移除任何元素。
示例  4:

输入:nums = [1,2,3], p = 7
输出:-1
解释:没有任何方案使得移除子数组后剩余元素的和被 7 整除。
示例 5:

输入:nums = [1000000000,1000000000,1000000000], p = 3
输出:0
 

提示:

1 <= nums.length <= 1e5
1 <= nums[i] <= 1e9
1 <= p <= 1e9

 

这个题还是用到(sum[r]-sum[l])%k==sum[r]%k-sum[l]%k

总体思路:用哈希表存储前缀和余数,通过余数找待移除的子数组

看不懂没关系往下看就懂了

首先,整个数组的和对p取余数,记录余数mod
然后开始遍历数组,求前缀和
对于前缀和数组,用哈希表记录其对p的余数及索引
也就是key为前缀和子数组的余数sub_mod,val为该前缀和数组的末尾索引i
在求前缀和及其余数的过程中,我们想要找哈希表中是否已经存在和当前余数相差为mod的前缀和子数组
因为如果两个前缀和子数组余数相差为mod,证明这两个数组相差部分构成的子数组就是对p取余数为mod的子数组
把这个子数组去掉,数组的剩余部分的和就可以被p整除
取满足条件的子数组的最小长度
还有一些细节如下,不想看的话可以直接看代码
i. 如果余数mod本身就是0无需移除子数组,返回0
ii. res已经为1的话可以进行剪枝,因为已经是最小的了,但也要注意如果数组长度也是1的话不可以剪枝,因为这样就把整个数组都移除了
iii. res如果为n的话说明没有符合条件的子数组,题目说了不允许将整个数组都移除,因此返回-1
iv. sub_mod可能比mod要小,为了避免target出现负数,target中多加了一个p,也就是target = (sub_mod - mod + p) % p

 

这个题我也没有理解为什么这样取连续的一段是可以的有待理解

所以说还是自行理解把

 

typedef long long ll;
class Solution {
public:
    int minSubarray(vector<int>& nums, int p) {
        ll mod=0,sum=0;
        int n=nums.size();
        for(auto x: nums){
            mod = (mod + nums[i])%p;
            sum += nums[i];
        }
        if(mod==0){
            return 0;
        }
        if(sum<p){
            return -1;
        }
        unordered_map<int, int> mp;
        ll s=0;
        int minlen = INT_MAX;
        mp[0]=-1;
        for(int i=0;i<n;i++){
            s=(s+nums[i])%p;
            int z=(s-mod+p)%p;
            if(mp.find(target) != mp.end())
            {
                minlen = min(minlen, i-mp[target]);
            }
            mp[s] = i;
        }
        return minlen >= nums.size() ? -1 : minlen;
    }
};

 

 

 

还有一好多类似的题传送门

LeetCode 523. 连续的子数组和(求余 哈希)
LeetCode 525. 连续数组(前缀和+哈希)
LeetCode 560. 和为K的子数组(前缀和差分)
LeetCode 974. 和可被 K 整除的子数组(哈希map)
LeetCode 1248. 统计「优美子数组」(要复习)

 



posted @ 2021-05-29 23:55  lipu123  阅读(311)  评论(0编辑  收藏  举报