同余定理+前缀和+状态压缩 == 解决连续子数组问题

Posted C_YCBX Py_YYDS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同余定理+前缀和+状态压缩 == 解决连续子数组问题相关的知识,希望对你有一定的参考价值。

题目一

在这里插入图片描述

解题分析

  • 同余定理:如果两个整数m、n满足n-m能被k整除,那么n和m对k同余

  • ( pre(j) - pre (i) ) % k == 0pre(j) % k == pre(i) % k

  • 推导 => pre (i) % k = (a0 + a1 + ... + ai) % k = (a0 % k + a1 % k + ... ai % k ) % k(该推导在简化前缀和的时候有用,说明当前前缀和 % k 不会影响后面的前缀和 % k )

  • 哈希表 存储 Key :pre(i) % k
    Value: i

遍历过程:

  1. 计算前缀和 pre( j ) % k

  2. pre(j) % k在哈希表中已存在,则说明此时存在 i 满足 pre(j) % k == pre(i) % k ( i < j )

  3. HashMap里,已知Key,可以取到Value 即i的值, 最后 判断j - i >= 2是否成立 即可

  4. pre(j) % k 不存在于哈希表,则将 (pre(j) % k, j ) 存入哈希表

  5. (状态压缩)因在计算pre(i) = (pre(i-1) + nums[i]) % k时,pre(i)只与上一个状态有关,故可以直接用变量pre 替代数组。 那么 求前缀和 % k 的公式就简化为 题解代码中的 remainder = (remainder + nums[i]) % k;

dfs爆搜(超时)

虽超时了,但是就差一个测试用例就过了
时间复杂度O(n^2)

class Solution {
public:
    bool checkSubarraySum(vector<int>& nums, int k) {
        t = k;
        //遍历所有子数组
        for(int i=0;i<nums.size();i++){
            s = i;
            dfs(nums,i,0);
            if(res)
                return true;
        }
        return false;
    }
private:
    bool res = false;
    int t ;
    int s;
    void dfs(vector<int>&nums,int k,int target){
        if(k-s>1&&target%t==0){
            res = true;
            return;
        }if(k==nums.size())
            return;
        dfs(nums,k+1,target+nums[k]);
    }
};

数组前缀和

在这里插入图片描述

时间复杂度O(n)空间复杂度O(n)

class Solution {
public:
    bool checkSubarraySum(vector<int>& nums, int k) {
        //用于存储前缀和
        vector<int>pre(nums.size()+1);
        unordered_map<int,int>hash;
        //把取余为0的包括在内
        hash[0] = 0;
        for(int i=1;i<=nums.size();i++){
            pre[i] = pre[i-1]+nums[i-1];
            //如果当前的前缀和取余之前出现过,则进行判断,否则入hash
           if(hash.count(pre[i]%k)){
               if(i-hash[pre[i]%k]>=2)
                    return true;
           }
           else{
               hash[pre[i]%k] = i;
           }
        }
        return false;
    }
};

最终的状态压缩(压缩用于存储前缀和的数组)版本

在这里插入图片描述

也就空间复杂度上少了4m。。。实际两个复杂度都是一样的。

class Solution {
public:
    bool checkSubarraySum(vector<int>& nums, int k) {
        //用于存储前缀和
        unordered_map<int,int>hash;
        //把取余为0的包括在内
        hash[0] = 0;
        int pre = 0;
        for(int i=1;i<=nums.size();i++){
            pre = pre+nums[i-1];
            //如果当前的前缀和取余之前出现过,则进行判断,否则入hash
           if(hash.count(pre%k)){
               if(i-hash[pre%k]>=2)
                    return true;
           }
           else{
               hash[pre%k] = i;
           }
        }
        return false;
    }
};

题目二(蓝桥杯例题)

在这里插入图片描述

唯一的不同在于:需要求解存在这样的区间和的次数,我们只需要在上一题的基础上把hash表中存储的值进行改变即可,此题明显需要记录某个同余的出现次数,一旦再次出现同余,则就会产生同余次数个区间个数(由于这题认可区间长度为1)。
大坑:注意前缀和用int型会爆,还有结果用int也会爆,所以都用long long型即可。

解题代码

由于蓝桥杯练习系统中不支持C++11所以用unordered_map需要调用tr1库命名空间也需要tr1.

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
using namespace std::tr1;
long long SubarraySum(vector<int>& nums, int k) {
        //用于存储前缀和
        unordered_map<int,int>hash;
        //把取余为0的包括在内
        hash[0] = 1;
        long long pre = 0;
        long long res = 0;
        for(int i=1;i<=nums.size();i++){
            pre = pre+nums[i-1];
            //如果当前的前缀和取余之前出现过,则更新答案,有多少个同余的对象就有多少个答案
           if(hash.count(pre%k)){
               res += hash[pre%k];
           }
           
               hash[pre%k]++;
           
        }
        return res;
}
int main(){
int n,k;
cin>>n>>k;
vector<int>nums(n);
for(int i=0;i<n;i++)cin>>nums[i];
long long res = SubarraySum(nums,k);
cout<<res;
return 0;
}

以上是关于同余定理+前缀和+状态压缩 == 解决连续子数组问题的主要内容,如果未能解决你的问题,请参考以下文章

使用同余定理 解答 523. 连续的子数组和

2017蓝桥杯 K倍区间 前缀和+同余定理

前缀和的一周---连续数组

ACM选修HUST1058(市赛题) Lucky Sequence 同余定理

HDU - 5776 同余(思维)

HDU - 5776 同余(思维)