前缀和 (二 )

题目:和为k的子数组

1.链接

560. 和为 K 的子数组 - 力扣(LeetCode)

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.代码实现

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.链接

1230. K倍区间 - AcWing题库

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.代码实现

#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.链接

4405. 统计子矩阵 - AcWing题库

2.大体思路

暴力解法-->(时间复杂度太高肯定过不了,但能得70分)

  • 先创建一个二维前缀和数组
  • 然后在4重for循环去枚举所有情况
#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.代码实现

#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;
}

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值