同余定理+前缀和+状态压缩 == 解决连续子数组问题
Posted C_YCBX Py_YYDS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了同余定理+前缀和+状态压缩 == 解决连续子数组问题相关的知识,希望对你有一定的参考价值。
题目一
解题分析
-
同余定理:如果两个整数m、n满足n-m能被k整除,那么n和m对k同余
-
即
( pre(j) - pre (i) ) % k == 0
则pre(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
遍历过程:
-
计算前缀和
pre( j ) % k
-
当
pre(j) % k
在哈希表中已存在,则说明此时存在 i 满足pre(j) % k == pre(i) % k ( i < j )
-
HashMap里,已知Key,可以取到Value 即i的值, 最后 判断
j - i >= 2
是否成立 即可 -
当
pre(j) % k
不存在于哈希表,则将(pre(j) % k, j )
存入哈希表 -
(状态压缩)因在计算
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;
}
以上是关于同余定理+前缀和+状态压缩 == 解决连续子数组问题的主要内容,如果未能解决你的问题,请参考以下文章