Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >前缀和算法详解

前缀和算法详解

作者头像
2的n次方
发布于 2024-10-15 08:03:26
发布于 2024-10-15 08:03:26
13700
代码可运行
举报
文章被收录于专栏:CSDN 迁移文章CSDN 迁移文章
运行总次数:0
代码可运行

对于查询区间和的问题,可以预处理出来一个前缀和数组 dp,数组中存储的是从下标 0 的位置到当前位置的区间和,这样只需要通过前缀和数组就可以快速的求出指定区间的和了,例如求 l ~ r 区间的和,就可以之间使用 dp[l - 1] - dp[r] 来计算

1. DP34 【模板】前缀和

DP34 【模板】前缀和

这里从下标 1 开始填是为了在初始化前缀和数组时更方便

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int p = sc.nextInt();
        int[] arr = new int[N + 1];
        long[] dp = new long[N + 1];
        for (int i = 1; i <= N; i++) {
            arr[i] = sc.nextInt();
        }
        for (int i = 1; i <= N; i++) {
            dp[i] = dp[i - 1] + arr[i];
        }
        int l = 0, r = 0;
        while (p-- != 0) {
            l = sc.nextInt();
            r = sc.nextInt();
            System.out.println(dp[r] - dp[l - 1]);
        }
    }
}

2. DP35 【模板】二维前缀和

二维前缀和模版

和一维的前缀和数组类似,这里需要先预处理出来一个前缀和矩阵 dp[][],dp[i][j] 就表示从(1,1)到(i,j)这个矩阵中的所有元素的和

放到矩阵中可以看出,如果想要求(1,1)到(i,j)区间内的区域和,需要先加上 A,B,C,D 四个区域的和,如果单独的表示 B 区域或者 C 并不好表示,但是 A + B 和 A + C 是很好表示的,把这两个区域加起来再减去多加的 A ,再加上 D 就是整个区域的和

得到了前缀和数组之后,该怎么去使用呢?

如果说给出了(x1,y1)(x2,y2)两个点,那么就是求红色框的元素的和

也就是求出 D 区域的和,由于 B 和 C 并不好单独转换,就可以转化为 A+B+C+D 的值先减去 A+B 的值,再减去 A + C 的值,此时方法 A 被多减了一次,再加上就是 D 区域的和了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(), m = sc.nextInt();
        int q = sc.nextInt();
        int[][] A = new int[n + 1][m + 1];
        long[][] dp = new long[n + 1][m + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                A[i][j] = sc.nextInt();
            }
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                dp[i][j] = dp[i][j - 1] + dp[i - 1][j] - dp[i - 1][j - 1]+ A[i][j];
            }
        }
        int x1 = 0,y1 = 0,x2 = 0,y2 = 0;
        while (q-- != 0) {
            x1 = sc.nextInt();
            y1 = sc.nextInt();
            x2 = sc.nextInt();
            y2 = sc.nextInt();
            System.out.println(dp[x2][y2] - dp[x2][y1 - 1] - dp[x1 - 1][y2] + dp[x1 - 1][y1 - 1]);
        }
    }
}

3. 寻找数组的中心下标

724. 寻找数组的中心下标

根据题目要求就是求 0~ i - 1区间的和是否和区间 i + 1~n - 1 的和相等,那么此题dp[i] 就表示的是[0,i-1] 区间的和而不是之前的 0 ~ i 区间的和了,相应的,状态转移方程也要有所变化

同时这道题还需要求一个后缀和数组用来描述后半段的区间和,也是同样的道理,只不过 dp[i] 表示的是[i + 1,n - 1] 区间的和,这段区间的状态转移方程也就是 dp[i+1] + nums[i + 1]

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
    public int pivotIndex(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        int[] g = new int[n];
        for (int i = 1; i < n; i++) {
            dp[i] = dp[i - 1] + nums[i - 1];
        }
        for (int i = n - 2; i >= 0; i--) {
            g[i] = g[i + 1] + nums[i + 1];
        }
        for (int i = 0; i < n; i++) {
            if (g[i] == dp[i]) {
                return i;
            }
        }
        return -1;
    }
}

4. 除自身以外数组的乘积

238. 除自身以外数组的乘积

这道题其实就和上道题非常类似,把 i 位置之前的数都求一个前缀积,之后的数求一个后缀积,只需要把前缀积和后缀积相乘就可以了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] f = new int[n];
        int[] g = new int[n];
        int[] ans = new int[n];
        f[0] = 1;
        g[n - 1] = 1;
        for (int i = 1; i < n; i++) {
            f[i] = f[i - 1] * nums[i - 1];
        }
        for (int i = n - 2; i >= 0; i--) {
            g[i] = g[i + 1] * nums[i + 1];
        }
        for (int i = 0; i < n; i++) {
            ans[i] = f[i] * g[i];
        }
        return ans;
    }
}

5. 和为 K 的子数组

560. 和为 K 的子数组

思路:每次都求0 ~ i - 1 区间内有多少个子数组是和为 k 的,如果正常求的话,时间复杂度就是O(n^2)了,所以说可以把子数组和为k的个数存在哈希表中去求,也就需要在求和的过程中就把这些数据添加到哈希表中,而求0 ~ i - 1 区间,和为 k 的子数组,就可以转化为求前半部分的哪段区间的和为整段区间和 sum - k

注意点:

  1. 不用真正的创建一个前缀和数组去表示和,只需要用一个 sum 变量来计算即可
  2. 如果说整个前缀和都等于k的话,就代表 sum - k 等于 0,这个需要提前在哈希表中存储,因为此时需要在 0~-1 区间内去找一个和为0的次数,但是这个区间不存在,所以说需要提前设置为 1,当需要查找的时候,就是默认的 1
  3. 前缀和加入到哈希表的时机:需要在计算i位置之前,保存0~i-1区间的前缀和,也就是知道 sum - k的次数,i-1统计之后才可以把i位置的前缀和存入哈希表中
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
    public int subarraySum(int[] nums, int k) {
        int sum = 0,ret = 0;
        HashMap<Integer,Integer> hash = new HashMap<>();
        hash.put(0,1);
        for(int x : nums){
            sum+=x;
            //以当前元素为结尾时有多少符合条件的答案
            ret+=hash.getOrDefault(sum - k,0);
            hash.put(sum,hash.getOrDefault(sum,0) + 1);
        }
        return ret;
    }
}

6. 和可被 K 整除的子数组

974. 和可被 K 整除的子数组

同余定理:(a - b) / p = k......0 => a % p = b % p

就是如果 a - b 的差能够被 p 整除的话,那么 a 和 b 对 p 取模就相等

Java 中,一个负数对一个正数取模的话得到的是一个负数,如果想要修正为正数的话可以使用下面的式子

首先把负数的余数变为正数,但如果原来就是正数的话还是不对的,所以需要再取一个余数

这样就可以把题目转化为只需要求在 i 之前有多少个区间对 K 取模之后等于 0 ,也就和上一题类似了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
    public int subarraysDivByK(int[] nums, int k) {
        int sum = 0, ret = 0;
        HashMap<Integer, Integer> hashMap = new HashMap<>();
        hashMap.put(0 % k, 1);
        for (int x : nums) {
            sum += x;
            int r = (sum % k + k)% k;
            ret += hashMap.getOrDefault( r, 0);
            hashMap.put(r, hashMap.getOrDefault(r, 0) + 1);
        }
        return ret;
    }
}

7. 连续数组

525. 连续数组

如果直接统计 0 和 1 的个数的话还是比较麻烦的,可以转化一下,把所有的 0 都变成 - 1,当 0 和 1 的个数相等时,也就是区间和等于 0 的情况,也就转化为了之前的求和为 k 的子数组的问题,只不过之前求的是个数,这题求的是长度,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
    public int findMaxLength(int[] nums) {
        HashMap<Integer, Integer> hash = new HashMap<>();
        hash.put(0, -1);
        int sum = 0, ret = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += (nums[i] == 0 ? -1 : 1);
            if (hash.containsKey(sum)) {
                ret = Math.max(ret, i - hash.get(sum));
            } else {
                hash.put(sum, i);
            }
        }
        return ret;
    }
}

需要注意的是,如果说在之后发现有重复的 sum 的时候,就不需要再存放进哈希表中了,因为此时的长度肯定是没有第一次的长的,就会影响后面使用 i - j 时计算的长度

8. 矩阵区域和

1314. 矩阵区域和

也就是周围所有元素的和

首先就是先预处理一个二维前缀和数组,然后再求( i , j ) 位置的值

求(i , j )位置的值的时候和之前讲的前缀和模版类似

然后就是怎么求坐标的问题,知道(i , j)坐标之后,求此处的值的话就需要求左上角和右下角的坐标,然后才能求出这个区域中的元素和

关于下标的映射关系,由于题目中的数组是从下标 0 计算的,为了方便是将 dp 表从下标为 1 开始计算的,所以说后续再继续从 dp 表中使用值的时候是需要把 x ,y 坐标都加上 1 的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
    public int[][] matrixBlockSum(int[][] mat, int k) {
        int m = mat.length, n = mat[0].length;
        int[][] dp = new int[m + 1][n + 1];
        int[][] ans = new int[m][n];
        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 = Math.max(0,i - k) + 1,y1 = Math.max(0,j - k) + 1;
                int x2 = Math.min(m - 1,i + k) + 1,y2 = Math.min(n - 1, j + k) + 1;
                ans[i][j] = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];
            }
        }
        return ans;
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
算法专题四: 前缀和
根据题意, 创建一个前缀和数组, dp[i] = dp[i -1] + arr[i], 再使用前缀和数组, 要求的区域ret = dp[r] - dp[l-1], 这里我们为什么要这样求dp[i]呢? 还要绕一大圈子, 直接相加不就行了 , 但是如果直接相加求还不如我们的暴力解法呢, 这里还要开辟空间, 但是我们使用dp[i]求解只需遍历一遍数组即可求出前缀和
用户11317877
2024/10/16
1310
算法专题四: 前缀和
【算法专题】前缀和
题目:给定一个长度为n的数组 a1​, a2​, …an. 接下来有q次查询, 每次查询有两个参数l, r. 对于每个询问, 请输出 al + al + 1 + … + ar
YoungMLet
2024/03/01
2000
【算法专题】前缀和
我爱学算法之—— 前缀和(下)
那如何判断子数组的和能否被k整除,统计次数呢?(依次判断时间复杂度就和暴力解法一样了)
星辰与你
2025/07/22
890
我爱学算法之—— 前缀和(下)
【算法】——前缀和(矩阵区域和详解,文末附)
三三是该溜子
2024/12/30
1610
【算法】——前缀和(矩阵区域和详解,文末附)
基础算法---前缀和
前缀和的用处:前缀和数组求出来之后我们就可以就可以求数组中的某个特定区间的和
用户11305458
2024/10/09
2010
基础算法---前缀和
初识算法 · 前缀和(1)
​本文的主题是前缀和,通过两道题目讲解,一道是一维数组的模板,一道是二维数组的模板。 链接分别为: 【模板】前缀和_牛客题霸_牛客网 【模板】二维前缀和_牛客题霸_牛客网 题目分为三个部分讲解,一是题目解析,二是算法原理,三是算法编写,那么,话不多说,直接进行主题咯。
_lazy
2024/11/19
1130
初识算法 · 前缀和(1)
【C++】前缀和算法专题
⽤ dp[i] 表⽰: [1, i] 区间内所有元素的和,那么 dp[i - 1] ⾥⾯存的就是 [1,i - 1] 区间内所有元素的和,那么:可得递推公式: dp[i] = dp[i - 1] +arr[i] ;
啊QQQQQ
2024/11/19
1560
【C++】前缀和算法专题
算法思想总结:前缀和算法
小陈在拼命
2024/03/23
1390
算法思想总结:前缀和算法
【优先算法】思还故里闾,欲归道无因 - 前缀和
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。
用户11369350
2025/01/20
1070
【优先算法】思还故里闾,欲归道无因 - 前缀和
前缀和与差分 Krains 2020-07-28 16:05:15
s[i]=a[0]+a[1]+a[2]+...+a[i−1]s[i] = a[0]+a[1]+a[2]+...+a[i-1]s[i]=a[0]+a[1]+a[2]+...+a[i−1]
Krains
2020/08/05
3030
【刷题】前缀和进阶
今天我们继续加强对前缀和算法。 前缀和算法是对数组进行预处理操作,进而避免大量重复的操作!使得算法性能增强! 适用于对数组有大量重复操作的问题,一维预处理较简单,二维比较复杂,画图分析可以顺利解决!
叫我龙翔
2024/05/11
1640
【刷题】前缀和进阶
一维 二维求前缀和、差分
用户10604450
2024/03/15
1210
我爱学算法之—— 前缀和(上)
通过上图,我们可以发现:我们要求的[l , r]区间的和s就等于区间[1 , r]的和 减去区间[1 , l]的和。
星辰与你
2025/06/08
880
我爱学算法之—— 前缀和(上)
【优选算法】Prefix-Kage:前缀和的算法影(下)
题解: 💻第一步: 要计算前后和相等的数的下标(不包括该数),显然要计算前后缀和
DARLING Zero two
2024/12/24
1300
【优选算法】Prefix-Kage:前缀和的算法影(下)
【优选算法篇】前缀之序,后缀之章:于数列深处邂逅算法的光与影
给定一个长度为 n 的整数数组 arr 和 q 个查询,每个查询由两个整数 l 和 r 组成,表示区间 [l, r]。请计算出每个区间内所有元素的和。
半截诗
2025/05/29
1470
【优选算法篇】前缀之序,后缀之章:于数列深处邂逅算法的光与影
【算法】前缀和问题
本题中的 dp[i] 表示的是 i 位置之前的前缀和,不包括 i 位置,所以 dp 表要多开一个位置,而且还要注意循环的区间。
_小羊_
2025/03/29
1690
【算法】前缀和问题
【前缀和的力量:高效解决子数组和矩阵问题的秘笈】—— 蓝桥杯高频热点题型知识点
前缀和(Prefix Sum)是一种常用的算法技巧,用于快速计算数组中某一范围的元素之和。
用户11286421
2025/03/17
1570
【前缀和的力量:高效解决子数组和矩阵问题的秘笈】—— 蓝桥杯高频热点题型知识点
【算法篇】前缀和
用户11029103
2025/01/15
1000
【算法篇】前缀和
【优选算法篇】前缀和与哈希表的完美结合:掌握子数组问题的关键(下篇)
引言:通过上篇文章带大家简单了解“前缀和算法”,小试牛刀。接下来将让大家感受一下前缀和在解题的妙处。
熬夜学编程的小王
2024/12/24
2340
【优选算法篇】前缀和与哈希表的完美结合:掌握子数组问题的关键(下篇)
【刷题】前缀和入门
☆ミヾ(∇≦((ヾ(≧∇≦)〃))≧∇)ノ彡☆ ☆ミヾ(∇≦((ヾ(≧∇≦)〃))≧∇)ノ彡☆ ☆ミヾ(∇≦((ヾ(≧∇≦)〃))≧∇)ノ彡☆
叫我龙翔
2024/04/25
1070
【刷题】前缀和入门
相关推荐
算法专题四: 前缀和
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验