前缀和篇——繁星斗斗数字交织中,觅得效率明月辉光(3)

在这里插入图片描述

前言

本篇带来前缀和的最终部分,与之前相同,将结合难度进一步提升的题目进行详细分析讲解,以深化对该算法的理解运用。

一. 和可被k整除的子数组

1.1 题目链接:https://leetcode.cn/problems/subarray-sums-divisible-by-k/description/

1.2 题目分析:

该题与上篇和为K的子数组要求类似,只是此处要求和可被K整除。

1.3 思路讲解

在此之前,我们先补充一下本题需要使用的数学知识:同余定理。

如果 (a - b) % n == 0 ,那么我们可以得到⼀个结论: a % n == b % n 。***⽤⽂字叙
述就是,如果两个数相减的差能被 n 整除,那么这两个数对 n 取模的结果相同。

例如: (26 - 2) % 12 == 0 ,那么 26 % 12 == 2 % 12 == 2 。

余数修正

c++ 中负数取模的结果,以及如何修正「负数取模」的结果:

a. c++ 中关于负数的取模运算,结果是「把负数当成正数,取模之后的结果加上⼀个负号」。 例如: -1 % 3 = -(1 % 3) =-1
b. 因为有负数,为了防⽌发⽣「出现负数」的结果,以 (a % n + n) % n 的形式输出保证为 正。 例如: -1 % 3 = (-1 % 3 + 3) % 3 = 2

暴力解法(会超时):
与之前相同,逐个遍历枚举。

前缀和:

  • . 首先,我们用sum[i]表示下标为i的元素的前缀和。
    如图,和可被K整除的子数组,即代表[x,i]内的元素和sum[i]-sum[x]可被k整除
    在这里插入图片描述
  • 由同余定理可得,(sum[i]-sum[x])%k==0,可得出sum[i]%k与sum[x]%k相等。
  • 因此问题就转化为,求有多少个前缀和可被k整除的数组,即找到在 [0, i - 1] 区间内,有多少前缀和的余数等于 sum[i] % k 的即可。
  • 同时由于负数取模的性质,在取模时需要对余数进行修正。

1.4 代码实现:

class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        unordered_map<int,int> hash;//记录前缀和及出现次数
        int sum=0,ret=0;
        hash[0%k]=1;
        for(int i=0;i<nums.size();i++)
        {
            sum+=nums[i];
            int r=(sum%k+k)%k;//修正余数
            if(hash.count(r))
            {
                ret+=hash[r];
            }//说明此时该子数组可被k整除
            hash[r]++;
        }
        
        return ret;
    }
};

二. 连续数组

2.1 题目链接:https://leetcode.cn/problems/contiguous-array/description/

2.2 题目分析:

题目要求找出最长的连续子数组,且该子数组内0和1的个数相等

2.3 思路讲解:

暴力解法(会超时):
枚举所有的子数组,判断是否满足条件。

前缀和:
题目的关键之处在于,所求的最长子数组内0和1的个数相等,暴力算法内的时间冗余也主要在判断是否相等上。
不妨将题目内的0变为-1,那么此时符合条件的子数组所有元素和则必然为0,大大简化了判断条件。

具体步骤如下:

  • 采用哈希表记录前缀和及其出现的位置,sum来记录当前的和,ret记录返回的数组长度
  • 当元素为0时,sum-1,当元素为1时,sum+1
  • 当满足条件时,更新ret,否则则更新哈希表

2.4 代码实现:

class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        unordered_map<int,int> hash;//记录前缀和及出现的位置
        
        int sum=0,ret=0;
        hash[0]=-1;//记录起始位置,防止最大子数组为数组本身的情况
        for(int i=0;i<nums.size();i++)
        {
            sum+=nums[i]==0?-1:1;
            if(hash.count(sum))
            {
                ret=max(ret,i-hash[sum]);
            }//更新ret
            else
            {
                hash[sum]=i;
            }

        }
        return ret;
        
    }
};

三. 矩阵区域和

3.1 题目链接:https://leetcode.cn/problems/matrix-block-sum/description/

3.2 题目分析:

本题看内容介绍较为抽象,可以把K理解为行列的扩充数,如题中所给示例:
1 2 3
4 5 6
7 8 9
此时k=1,因此answer[0][0]即为在原矩阵mat[0][0]的基础上行列各扩张一行,此时区域内的元素就包括mat[0][0],mat[0][1],mat[1][0],mat[1][1],因此和为1+2+4+5等于12.
注意:在扩充时如果超出矩阵本身界限,则超出部分不予处理。

3.3 思路讲解:

在第一篇中,我们详细讲解了二维矩阵的前缀和计算方法,因此在此处不再加以赘述,着装讲解本题思路。
具体步骤如下:

  • 首先,由于本题要处理边界问题,我们首先求出行m和列n,并构建前缀和矩阵
  • 在进行扩充时,注意矩阵区域和的左右边界均不可超出矩阵本身
  • 前缀和矩阵dp[i][j]的求法在第一篇中有详细说明,如果存在遗忘可移步进行查看。

第一篇链接:https://blog.csdn.net/2303_81060385/article/details/144199070?spm=1001.2014.3001.5502

3.4 代码实现:

class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
        int m=mat.size(),n=mat[0].size();
        vector<vector<int>> dp(m+1,vector<int>(n+1));//构建前缀和矩阵
        vector<vector<int>> ret(m,vector<int>(n));//返回矩阵
        //处理前缀和数组
        for(int i=1;i<m+1;i++)
        {
            for(int j=1;j<n+1;j++)
            {
                dp[i][j]=dp[i-1][j]+dp[i][j-1]+mat[i-1][j-1]-dp[i-1][j-1];
            }
        }
        //使用前缀和数组
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
               int x1=max(0,i-k)+1,y1=max(0,j-k)+1;//确定左上边界
               int x2=min(m-1,i+k)+1,y2=min(n-1,j+k)+1;//确定右下边界
                ret[i][j]=dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1]+dp[x1-1][y1-1];
            }
        }
        return ret;


        
    }
};

小结

关于前缀和算法的讲解就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!
在这里插入图片描述

评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值