当前位置: 首页 > news >正文

【算法】前缀和(下)

 

目录

一、和为k的子数组

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

三、连续数组

四、矩阵区域和


、和为k的子数组

题目链接:560. 和为 K 的子数组 - 力扣(LeetCode)

题目描述

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

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

示例 1:

输入:nums = [1,1,1], k = 2输出:2

示例 2:

输入:nums = [1,2,3], k = 3输出:2

提示:

  • 1 <= nums.length <= 2 * 10^4
  • -1000 <= nums[i] <= 1000
  • -10^7 <= k <= 10^7

题目分析

其实就是在数组nums中,找出 和为k 的子数组并统计个数。注意:子数组是连续非空的

解题思路

设i为数组中的任意位置,使用sum[i]表示[0,i]区间内的所有元素之和

我们想知道有多少个【以i为结尾的 和为k的子数组】,则需要在[0,i-1]区间内找到 起始位置x1,x2,...;使得[x,i]区间内的元素和为k。那么[0,x-1]区间内的元素之和,便等于sum[i]-k

于是问题就转变为:

·在区间[0,i-1]内,找到有几个 前缀和 等于sum[i]-k 的即可

注意:本题我们只关心在i位置之前,有多少个 前缀和 等于sum[i]-k。所以不需要额外初始化一个前缀和数组,我们直接使用哈希表,一边求当前位置的 前缀和,一边存下 上一位置 前缀和出现的次数

注意:当sum[i]-k=0时,则表示sum[i]此时等于k。这便表示在[0,i]区间内,有一个起始位置满足了前缀和等于sum[i]-k。所以我们需要在哈希表中,初始化key=0时,value=1。

如果nums=[1],k=1时,上述初始化能够保证算出一个符合sum[i]-k的起始位置

简单来说,就是记录下[0,i]区间内的前缀和,当在[x,i]区间内的元素和为k时,然后在[0,x-1]区间内找到前缀和 为sum[i]-k的起始位置即可。

在代码中的话,我们应该在计算完前缀和后,判断哈希表中key=sum[i]-k的value值,如果非0,则统计下来。之后,哈希表再进行记录前缀和

解题代码

Public static int subarraySum(int[] nums, int k) {

    Map<Integer,Integer> hash=new HashMap<>();

    hash.put(0,1);//初始化,当sum[i]-k=0时,有一个起始位置满足情况

    int sum=0;

    int ret=0;

    for(int x:nums){

        sum+=x;//计算当前位置的前缀和

        //只要当key=sum-k时,value不为0,那么记录下此时有几个起始位置

        ret+=hash.getOrDefault(sum-k,0);//记录有多少个起始位置

        hash.put(sum,hash.getOrDefault(sum,0)+1);//把当前位置的 前缀和 放在哈希表中

    }

    return ret;

}

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

题目链接:974. 和可被 K 整除的子数组 - 力扣(LeetCode)

题目描述:

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

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

示例 1:

输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

示例 2:

输入: nums = [5], k = 9
输出: 0

提示:

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

题目分析:

其实本题就是找到  元素和可以整除k的子数组的个数

解题思路和上一次类似

在解决本题之前,我们需要了解以下知识

同余定理

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

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

c++中负数取模的结果,以及如何修正【负数取模】的结果

·c++中关于负数的取模运算,结果是【把负数当成整数,取模之后的结果加上一个负号】

如:-1%3=  -(1%3)= -1

·因为有负数,为了防止发生【出现负数】的结果,以(a%n+n)%n的形式输出保证为正

如: -1%3=( -1%3+3)%3=2

解题思路:

设i为数组中的任意位置,用sum[i]表示区间[0,i]内所有元素的和

我们想知道有多少个【以i为结尾的 可被k整除的子数组】,就需要找到有多少个起始位置为x1,x2...使得[x,i]区间内的所有元素和 可被k整除

设[0,x-1]区间内所有元素之和为a,[0,i]区间内所有元素之和为b,那么(b-a)%k==0

由同余定理可得,[0,x-1]区间与[0,i]区间内的前缀和同余。

于是问题就转变为:在[0,x-1]区间内,找到有多少 前缀和的余数 等于 sum[i]%k 即可

简单来说,就是记录下[0,i]区间内的前缀和,然后在[0,x-1]区间内找到前缀和的余数 等于sum[i]%k即可。

在代码中的话,我们应该在计算完前缀和后,判断哈希表中key=(a%n+n)%n的value值,如果非0,则统计下来。之后,哈希表再进行记录前缀和

解题代码:

class Solution {
    public static int subarraysDivByK(int[] nums, int k) {
        int n = nums.length;
        int[] s = new int[n];
        s[0] = nums[0];
        for (int i = 1; i < n; i++) {
            s[i] = s[i - 1] + nums[i];
        }
        int res=0;
        Map<Integer,Integer> cnt=new HashMap<>();
        for(int x:s){
            int t=(x%k+k)%k;
            if(t==0) res++;
            res+=cnt.getOrDefault(t,0);
            cnt.put(t,cnt.getOrDefault(t,0)+1);
        }
        return res;
    }
}

三、连续数组

题目链接:525. 连续数组 - 力扣(LeetCode)

题目描述:

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

示例 1:

输入:nums = [0,1]
输出:2
说明:[0, 1] 是具有相同数量 0 和 1 的最长连续子数组。

示例 2:

输入:nums = [0,1,0]
输出:2
说明:[0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。

示例 3:

输入:nums = [0,1,1,1,1,1,0,0,0]
输出:6
解释:[1,1,1,0,0,0] 是具有相同数量 0 和 1 的最长连续子数组。

提示:

  • 1 <= nums.length <= 10^5
  • nums[i] 不是 0 就是 1

注意:题目已告知nums[i]不是0就是1

我们这里同样将问题进行转变。我们将元素为0的改为-1,元素为1的不变。

这样,我们只需要找到 和为0的最长连续子数组

这样,本题与 和为k的子数组 的解题思路就一致了

解题思路:

设i为数组中的任意位置,用sum[i]表示[0,i]区间内所有元素的和

我们想知道最大的【以i为结尾的 和为0的子数组】,就要找到从左往右第一个起始位置x1,使得[x1,i]区间内的所有元素之和为0.那么[0,x1-1]区间内的元素之和 就是sum[i]了。

于是问题就转变为:在[0,x-1]内,找到第一次出现sum[i]的位置即可

我们这里同样使用哈希表,一边计算前缀和,一边判断此时的key=sum是否存在哈希表中,如果存在的话,则计算与此处的距离;如果不存在,则将sum存入哈希表中

初始化key=0,value=-1的原因

如下图:

当出现第一次sum=0时,如果我们不初始化key=0的情况,我们就不能进入判断语句,进行计算数组长度。

当初始化key=0时,value值的设置又成了一大问题。

在计算数组长度时,我们用下标相减来计算。

由于下标从0开始,所以我们需要在下标相减后进行+1

所以,我们可以将value值设置为-1。这样下标相减时,-(-1)就变成了下标+1

将key=0,value=-1存入哈希表,还能解决整个数组和为0的情况。即:最后一个元素下标+1

解题代码:

class Solution {
    public static int findMaxLength(int[] nums) {
        Map<Integer,Integer> hash=new HashMap<>();
        hash.put(0,-1);//默认一个前缀和为0的情况
        int sum=0;
        int ret=0;
        for(int i=0;i<nums.length;i++){
            sum+=(nums[i]==0?-1:1);
            if(hash.containsKey(sum)){//判断在[0,x-1]区间内是否存在 前缀和为sum[i]的
                ret=Math.max(ret,i-hash.get(sum));
            }else hash.put(sum,i);//只记录第一次出现的sum
        }
        return ret;
    }
}

四、矩阵区域和

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

题目描述:

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

  • i - k <= r <= i + k,
  • j - k <= c <= j + k 且
  • (r, c) 在矩阵内。

示例 1:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 1
输出:[[12,21,16],[27,45,33],[24,39,28]]

示例 2:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 2
输出:[[45,45,45],[45,45,45],[45,45,45]]

提示:

  • m == mat.length
  • n == mat[i].length
  • 1 <= m, n, k <= 100
  • 1 <= mat[i][j] <= 100

题目分析:

根据题目中:

  • i - k <= r <= i + k,
  • j - k <= c <= j + k

我们可以知道矩阵的行与列的区间。也就是矩阵的左上角和右下角。然后根据【前缀和(上)】的二维前缀和的公式即可解题

解题思路:

二维前缀和的简单应用题,关键就是我们在填写结果矩阵的时候,要找到原矩阵对应区域的【左上角】以及【右下角】的坐标--这里推荐大家画图

左上角坐标:x1=i-k,y1=j-k,但是这里可能会【超过矩阵】的范围,因此需要对0取一个max。因此修正后的坐标为:x1=max(0,i-k),y1=max(0,j-k)

右下角坐标:x2=i+k,y2=j+k,这里仍然可能会【超过矩阵】的范围,因此需要对m-1,以及n-1去一个min。因此修正后的坐标为:x2=min(m-1,i+k),y2=min(n-1,j+k)

然后将求出来的坐标代入到【二维前缀和矩阵】的计算公式上即可

解题代码:

public static int[][] matrixBlockSum(int[][] mat, int k) {
        int m=mat.length;
        int n=mat[0].length;
        //前缀和矩阵
        int[][] sum=new int[m+1][n+1];
        //处理前缀和矩阵
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                sum[i][j]=sum[i-1][j]+sum[i][j-1]
                        -sum[i-1][j-1]+mat[i-1][j-1];
            }
        }
        int[][] answer=new int[m][n];
        //计算下标--我们确定好矩阵的左上角和右上角即可
        //即i和j的最小值和最大值
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                //注意:需要判断左上角和右上角的下标是否越界
                //我们这里通过max和min让其满足不会越界
                int x1=Math.max(0,i-k)+1;//前缀和矩阵多加一行和一列的0,所以需要+1
                int y1=Math.max(0,j-k)+1;
                int x2=Math.min(m-1,k+i)+1;
                int y2=Math.min(n-1,k+j)+1;
                answer[i][j]=sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
            }
        }
        return answer;
    }

相关文章:

  • 【Django】教程-12-柱状图
  • 5.JVM-G1垃圾回收器
  • 顺序栈简记
  • 为什么选择Redis?解析核心使用场景与性能优化技巧
  • QML面试笔记--UI设计篇02布局控件
  • 山东大学计算机网络第五章习题解析
  • 虚拟表、TDgpt、JDBC 异步写入…TDengine 3.3.6.0 版本 8 大升级亮点
  • 数字电子技术基础(四十)——使用Digital软件和Multisim软件模拟显示译码器
  • PyTorch 生态迎来新成员:SGLang 高效推理引擎解析
  • JMeterPlugins-Standard-1.4.0 插件详解:安装、功能与使用指南
  • “拈彩”测试报告
  • 【力扣刷题实战】全排列II
  • JavaScript惰性加载优化实例
  • day22 学习笔记
  • 算法卷一:起行
  • 深入剖析C语言中的指针与数组
  • const let var 在react jsx中的使用方法 。
  • 蓝桥杯真题—路径之谜
  • 一文掌握 google浏览器插件爬虫 的制作
  • springboot-4S店车辆管理系统
  • 整站优化全网营销/长沙网络营销公司排名
  • 上海网站建设技术/网络营销与管理
  • 小区网站建设/百度电脑版网址
  • 软件系统网站建设/个人免费网上注册公司
  • 网站信息备案管理系统/线上销售怎么做推广
  • asp做网站搜索/搜索最多的关键词的排名