题目:和为k的子数组

1.链接
2.大体思路

要找出有区间 [ i , j ] 子数组的和为k ,那么我们只要去找看从 [0,i-1]这端区间内有几个前缀和为sum-k(注意这里区间 [ i , j ] 只是一个假想的,i应该是0<=i<=j<=n-1)
故
- 我们只是需要先让 j ,从前往后依次遍历到 n- 1
- 然后去查询在 j 的前面有多少个前缀和为:sum-k
此刻如果我们采取遍历的方法去查询,那么就会因为时间复杂度为0(1*1+2*2+..+n*n)而通过不了。这里我们可以用哈希表来统计一下在 j 前面出现次前缀和为 sum-k
- 那么我们就利用unordered_map来统计
- 我们并不是一次全部统计好sum-k的次数,这样会造成 sum-k 前缀和区间不在 [ 0 , j ]里
- **我们这要先初始化hash [ 0 ] =1---->**这个是为了处理当第一个数据就为k的这种情况
3.代码实现
cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> hash;
hash[0] = 1;//这个时为了处理当第一个数据就为k的这种情况
int sum = 0, ret = 0;
for (auto x : nums) {
sum += x;
if (hash.count(sum - k))//如果hash表中存在sum-k,那么返回其数目,不存在,返回0
ret += hash[sum - k];
hash[sum]++;//统计次数
}
return ret;
}
};
题目:K倍区间

1.链接
2.大体思路
前提:q ->存放初始数据,s-->存放的是前缀和
- 先构建好一个前缀和数组s
- 然后再去让 j 从前往后去遍历,去看 在 [ 0 , j ]这个区间里有多少个前缀和满足( sum[ j ] -sum [ i ] )%k==0
这里我们要用同余定理来优化 (sum[ j ] -sum [ i ] )%k==0 <==> sum[ j ]%k==sum[ i ]%k
那么只就和我们上面那道题的思路是一样的,我们只要去统计在 j 的前面有多少个前缀和满足了sum[ i ] % k==sum[ j ] %k
3.代码实现
cpp
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;
const int N=1e5+10;
long long q[N],sum[N];
//前缀和数组要用long long 数据类型的,否则数据会爆
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
int k;cin>>k;
q[i]=k;
sum[i]=sum[i-1]+q[i];
}
long long cnt=0;
unordered_map<int,int> hash;
//键-->代表前缀和的余数,value-->代表这个余数出现次数
hash[0]=1; //-->表示当只要一个元素的前缀和就正好为k时
for(int i=1;i<=n;i++)
{
int r=sum[i]%k;
if(hash.count(r)) cnt+=hash[r];
hash[r]++;
}
cout<<cnt;
return 0;
//利用同余定理来优化
// for(int j=n;j>0&&i!=j;j--)
// {
// int kk=sum[j]-sum[i]; // 1.(sum[j]-sum[i])%k <==>sum[j]%k==sum[i]%k
// 2.当有sum[1]%k==0时,我们也要加1--》故我们要将 hash[0]=1
// // cout<<kk<<endl;
// if(kk%k==0)
// {
// count++;
// }
// if(kk<k) break;
// }
}
题目:统计子矩阵(蓝桥杯真题)

1.链接
2.大体思路
暴力解法-->(时间复杂度太高肯定过不了,但能得70分)
- 先创建一个二维前缀和数组
- 然后在4重for循环去枚举所有情况
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
typedef long long ll;
int n, m, k;
int s[N][N];
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++) {
cin >> s[i][j];
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
ll res = 0;
for (int x1 = 1; x1 <= n; x1 ++)
for (int y1 = 1; y1 <= m; y1 ++)
for (int x2 = x1; x2 <= n; x2 ++)
for (int y2 = y1; y2 <= m; y2 ++) {
if (s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] <=k)
res ++;
}
cout << res;
return 0;
}
优化方法:前缀和+双指针
我们这里让数据从( 1, 1)开始去存放,为了避免边界处理问题(前缀和一中提过)
- 先创建一个二维数组 s ,s[ i ] [ j ] --->表示第j列,从1到i这些元素之和
- 我们先用 i j 这两个指针来维护上下边界,然后再用 l , r 维护左右边界
- 接下来思路在代码上讲
3.代码实现
cpp
#include<iostream>
#include<vector>
using namespace std;
const int N=510;
typedef long long ll;
int s[N][N];
int n,m,k;
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&s[i][j]);
s[i][j]+=s[i-1][j];//执行这个代码前s[i][j]--表示原二维数组中a[i][j]的数值,
//执行后,s[i][j]-->表示第j列,从1到i这些元素之和
}
}
ll res=0;
//接下来用i,j维护上下边界,r,l-维护左右边界
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
for(int l=1,r=1,sum=0;r<=m;r++)
{
sum+=s[j][r]-s[i-1][r];
//-->表示让sum加上第r列上[i,j]的数据
while(sum>k)
{
sum-=s[j][l]-s[i-1][l];//让sum减去第l列上从[i,j]的数据
//为什么要l往右移动,而不是让r往左移动--> 不让r往左移动,是因为前面的已经统记了
//让l往右移动,是为了统计以[l,r]为左右边界的数据(注意此刻l已经发生变化)
l++;
}
res+=r-l+1;
}
cout<<res;
return 0;
}