【优选算法---前缀和】和为K的子数组、和可被K整除的子数组、连续数组、矩阵区域和

一、和为K的子数组

题目链接:

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

题目介绍:

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k的子数组的个数

子数组是数组中元素的连续非空序列。

  • 1 <= nums.length <= 2 * 104
  • -1000 <= nums[i] <= 1000
  • -107 <= k <= 107
思路:

对于暴力解法来说,我们需要确定一个起始下标,然后向后遍历,如果碰到一个区间之和为K了,记录一下,但是此时还需要向后遍历,因为数组中的元素可能存在0和负数,这样暴力解法的时间复杂度为O(n^2).

如果我们可以创建出一个前缀和数组,dp[i]表示0~i位置的和。那我们就可以通过访问一遍数组下标,以i为数组终点,我们要找一个下标到i位置的和为k,那0到这个下标之前的一个元素的和就是的dp[i]-k。

也就是说,我们要找以i位置为终点,找i位置前有多少个前缀和等于dp[i]-k。但是仔细想一下,创建前缀和数组时间复杂度为O(n),遍历每一个下标时,还需要遍历它之前的前缀和数组,这样的时间复杂度为O(n^2),看起来比暴力解法还复杂。

其实我们只关心 i 位置之前,所以我们并不需要真正创建一个前缀和数组,而且我们也没必要遍历前缀和数组,只需要创建一个哈希表,维护i位置之前前缀和与个数的映射即可,这个过程可以一遍求前缀和,一遍建立映射。

hash[0]=1, 是因为 前缀和 为0的默认就有一个。假设0~i这段区间的和正好为k,那我们就要去(0~-1)这个区间找前缀和为0的个数了,但是这个区间是不存在的,为了加上这个情况,所以将hash[0]设置为1

复制代码
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int,int> hash;
        hash[0]=1;
        int sum=0;
        int ret=0;
        for(auto e:nums)
        {
            sum+=e;
            if(hash.count(sum-k))
            ret+=hash[sum-k];
            hash[sum]++;
        }
        return ret;
    }
};

二、和可被K整除的子数组

题目链接:

974. 和可被 K 整除的子数组 - 力扣(LeetCode)

题目介绍:

给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的非空 子数组 的数目。

子数组 是数组中 连续 的部分。

  • 1 <= nums.length <= 3 * 10^4
  • -104 <= nums[i] <= 10^4
  • 2 <= k <= 10^4
思路:

在写这个题之前我们需要知道两个知识:

  • 同余定理: (a-b)/p=k .....0 ---> a%p == b%p。用文字叙述就是,如果两个数相减的差能被 n 整除,那么这两个数对 n 取模的结果相同。
  • c++修正负数%正数为负数: (a%p+P)%p 【加P是为了让他变为正数,在取模是为了本来是正数的受影响】

同余定理验证:

了解了同余定理,再看这个题目,我们要找一个区间(x~i)的和可以被k整除,假设0~x-1区间的和为a, x~i区间的和为b,题目的要求也就是 (b-a)%k=0.

由同余定理可得, [0, x - 1] 区间与 [0, i] 区间内的前缀和同余。于是问题就变成:

找到在 [0, i - 1] 区间内,有多少前缀和的余数等于 sum[i] % k 的即可

复制代码
class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        //同余定理: (a-b)/p=k .....0   ->      a%p  ==  b%p
        //c++修正负数%正数为负数: (a%p+P)%p
        unordered_map<int,int> hash;
        hash[0]=1;
        int sum=0,ret=0;
        for(auto e:nums)
        {
            sum+=e;
            int r=(sum%k+k)%k;
            if(hash.count(r))
            ret+=hash[r];
            hash[r]++;
        }
        return ret;
    }
};

三、连续数组

题目链接:

525. 连续数组 - 力扣(LeetCode)

题目介绍:

给定一个二进制数组 nums , 找到含有相同数量的 01 的最长连续子数组,并返回该子数组的长度。

思路:

如果将所有的0都换为-1,那这个题找的就是和为0的最长子数组,这个题就与和为K的子数组相同了,不一样的是这个题要的是最长子数组的长度。

思路依旧是以i为终点,找i之前前缀和为sum[i]的下标计算长度,这里有个问题,如果之前存在前缀合为sum的下标,后面就不能直接更新了,因为肯定是以前那个下标到i的距离长,只有不存在sum时才会更新sum映射的下标

复制代码
class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        unordered_map<int,int> hash;
        hash[0]=-1;
        int sum=0,ret=0;
        for(int i=0;i<nums.size();i++)
        {
            sum+=nums[i]==0?-1:1;
            if(hash.count(sum))
            ret=max(ret,i-hash[sum]);
            else
            hash[sum]=i;
        }
        return ret;
    }
};

四、矩阵区域和

题目链接:

1314. 矩阵区域和 - 力扣(LeetCode)

题目介绍:

给你一个 m x n 的矩阵 mat 和一个整数 k ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和:

  • i - k <= r <= i + k,
  • j - k <= c <= j + k
  • (r, c) 在矩阵内。
  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n, k <= 100
  • 1 <= mat[i][j] <= 100
思路:

这个题光看示例很难理解,我们以一个例子来讲这个题是要干什么

假设我们要求answer[0][0]的值,k为1.

其实就是求从(0,0)向四周扩展1格,然后求这个扩展后的矩形所有元素的和

与二维前缀和那个题目思路相同,先创建出一个二维前缀和数组,dp[i][j]表示以(0,0)为左上角,(i,j)为右下角的矩形的和。想要计算某个区域的面积,只需要提供这个区域两个角的坐标就可以了。

但是构建二维前缀和数组和使用是与之前那个题不一样的,因为那个题的二维前缀和数组的下标适合矩形的下标正好对应的,但是这个题为了方便解决边界情况,我们还需要将二维前缀和数据多加一行一列,但是这个题给的矩阵是从(0,0)开始的,所以我们定义的这个数组要映射到矩阵的话就要减1,同理,从矩阵映射到数组的话就要加1。

另外,因为我们要求左上角和右下角的坐标,但我们向外扩展时,坐标可能会到边界外,需要控制一下,因为要使用dp表时,可以在计算下表时就把1都加上

复制代码
class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
        int m=mat.size(),n=mat[0].size();
        vector<vector<int>> answer(mat.size(),vector<int>(mat[0].size()));
        vector<vector<int>> dp(mat.size()+1,vector<int>(mat[0].size()+1));
        //初始化前缀和
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+mat[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;
                int y1=max(0,j-k)+1;
                int x2=min(m-1,i+k)+1;
                int y2=min(n-1,j+k)+1;
                answer[i][j]=dp[x2][y2]-dp[x2][y1-1]-dp[x1-1][y2]+dp[x1-1][y1-1];
            }
        }
        return answer;
    }
};
相关推荐
TPBoreas1 小时前
Jenkins 改完端口号启动不起来了
java·开发语言
金斗潼关1 小时前
SpringCloud GateWay网关
java·spring cloud·gateway
JK0x071 小时前
代码随想录算法训练营 Day40 动态规划Ⅷ 股票问题
算法·动态规划
Feliz..1 小时前
关于离散化算法的看法与感悟
算法
水蓝烟雨2 小时前
1128. 等价多米诺骨牌对的数量
算法·hot 100
codists2 小时前
《算法导论(第4版)》阅读笔记:p11-p13
算法
秋名RG2 小时前
深入解析建造者模式(Builder Pattern)——以Java实现复杂对象构建的艺术
java·开发语言·建造者模式
eternal__day2 小时前
Spring Boot 实现验证码生成与校验:从零开始构建安全登录系统
java·spring boot·后端·安全·java-ee·学习方法
陈大爷(有低保)3 小时前
swagger3融入springboot
java
Kidddddult4 小时前
力扣刷题Day 43:矩阵置零(73)
算法·leetcode·力扣