【Java 优选算法】前缀和(上)

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗~

如有错误,欢迎指出~



一维前缀和

题目链接

解法

理解题意

下标从1开始计,如示例1:

  1. 输入第一行3 2 表示数组长度n=3,要询问q=2次,
  2. 第二行1 2 4 表示该数组为[1, 2, 4],
  3. 第三行表示第一次查询,要算下标为1到2对应数的和,
  4. 第四行表示第二次查询 要计算下标为2到3对应数的和

暴力解法O(n* q): 直接根据题意加和

解法二: 前缀和(快速求出数组中某一个连续区间的和) O(q) + O(n) 用空间换时间

  1. 预处理出来一个前缀和数组dp[i] ,该数组表示[1, i]区间内所有元素的和(比如dp[2]表示前2个数的和, dp[3]是前3个数的和...) dp[i] = dp[i - 1] + arr[i] , arr是原数组
  2. 使用前缀和数组 若要计算[l, r]区间的和, 直接dp[r] - dp[l - 1]

细节处理: 不从0下标开始是因为如果要计算0到1的和时,会出现dp[1] - dp[-1]的情况,越界了 . 假设要计算1到2的和,则有dp[2] - dp[0], 将dp[0]初始化为0就行了

画图举例

代码

复制代码
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        //1.读入数据
        int n = in.nextInt(), q = in.nextInt();
        int[] arr = new int[n + 1];
        for(int i = 1; i <= n; i++) arr[i] = in.nextInt();

        //2.预处理一个前缀和数组
        long[] dp = new long[n + 1];//防止溢出
        for(int i = 1; i <= n; i++) dp[i] = dp[i - 1] + arr[i];

        //3.使用前缀和数组
        while(q > 0){
            int l = in.nextInt(), r = in.nextInt();
            System.out.println(dp[r] - dp[l - 1]);
            q--;
        }
        
    }
}

二维前缀和

题目链接

解法

理解题意: 输入第1行 3 4 3 表示n = 3行, m = 4列的二维数组,要查询q = 3次

第2行到第4行表示该二维数组

后面3行分别表示3次查询,如第5行 1 1 2 2表示要算出(1, 1)为左上角,(2, 2)为右下角的二维数组的和,6和7行以此类推...

暴力解法: 直接求和,(最差情况是q次询问的每次都要求 求整个区间的和,每次都要遍历一遍数组,所以时间复杂度是: O(n * m * q) --> 会超时)

解法二: 前缀和 (时间复杂度: O(m * n) + O(q) )

  1. 预处理出来一个前缀和矩阵dp[i][j] ,表示从[1, 1]为左上角,[i, j]为右下角组成的二维数组所有元素的和, 将arr数组分为A B C D四个部分,利用面积关系得出 dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + arr[i][j] - dp[i - 1][j - 1]
  2. 使用前缀和矩阵

画图举例

代码

复制代码
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        //1.读入数据
        int n = in.nextInt(), m = in.nextInt(), q = in.nextInt();
        int[][] arr = new int[n + 1][m + 1];
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                arr[i][j] = in.nextInt();
            }
        }
        //预处理一个前缀和矩阵
        long[][] dp = new long[n + 1][m + 1];
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + arr[i][j] - dp[i - 1][j - 1];
            }
        } 
        //使用前缀和矩阵
        while(q > 0){
            int x1 = in.nextInt(), y1 = in.nextInt(), x2 = in.nextInt(), y2 = in.nextInt();
            System.out.println(dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1]);
            q--;
        }
        
    }
}

寻找数组的中心下标

题目链接

解法

利用前缀和思想: 定义一个前缀和f[i],和一个后缀和g[i]

  • 前缀和 f[i] 表示[0, i - 1]区间所有元素的和 , f[i] = f[i - 1] + nums[i - 1]
  • 后缀和 g[i] 表示 [i + 1, n - 1]区间所有元素的和 , g[i] = g[i + 1] + nums[i + 1]

列举下标0 ~ n - 1的中判断f[i] == g[i]

细节问题: 初始化f[0]和g[n - 1], f[0] 表示[-1, 0]区间的值 即f[0]=0, g[n - 1]同理, g(n - 1) = 0;

填表顺序: f表要从左向右填, g表要从右向左填

代码

复制代码
class Solution {
    public int pivotIndex(int[] nums) {
        int n = nums.length;
        int[] f = new int[n], g = new int[n];
        //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];
        }
        //2.使用
        for(int i = 0; i < n; i++){
            if(f[i] == g[i]) return i;
        }
        return -1;
    }
}

除自身以外数组的乘积

题目链接

解法

暴力解法: 每次都遍历一遍数组,将除了nums[i]的元素相乘,时间复杂度O(n^2)

解法二; 前缀积 f[i] 和后缀积 g[i],

  • f[i]表示: [0, i - 1]区间内所有元素的乘积, f[i] = f[i - 1] * nums[i - 1]
  • g[i]表示: [i + 1, n - 1]区间内所有元素的乘积, g[i] = g[i + 1] * nums[i + 1]

使用前缀积 : 使用ret数组表示结果, 将f[i] * g[i]填入ret的 i 位置

复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] f = new int[n], g = new int[n];

        //1.预处理一个前缀积数组和后缀积
        f[0] = 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];
        }
        int[] ret = new int[n];
        for(int i = 0; i < n; i++){
            ret[i] = f[i] * g[i];
        }
        return ret;
    }
}

处理细节: f[0] = 1, g[n - 1] = 1;

代码

复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] f = new int[n], g = new int[n];

        //1.预处理一个前缀积数组和后缀积
        f[0] = 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];
        }
        int[] ret = new int[n];
        for(int i = 0; i < n; i++){
            ret[i] = f[i] * g[i];
        }
        return ret;
    }
}
相关推荐
软件派2 分钟前
基于YOLO算法的目标检测系统实现指南
算法·yolo·目标检测
代码小将2 小时前
Leetcode209做题笔记
java·笔记·算法
Musennn3 小时前
leetcode 15.三数之和 思路分析
算法·leetcode·职场和发展
CM莫问6 小时前
<论文>(微软)避免推荐域外物品:基于LLM的受限生成式推荐
人工智能·算法·大模型·推荐算法·受限生成
康谋自动驾驶7 小时前
康谋分享 | 自动驾驶仿真进入“标准时代”:aiSim全面对接ASAM OpenX
人工智能·科技·算法·机器学习·自动驾驶·汽车
C++ 老炮儿的技术栈7 小时前
什么是函数重载?为什么 C 不支持函数重载,而 C++能支持函数重载?
c语言·开发语言·c++·qt·算法
yychen_java8 小时前
R-tree详解
java·算法·r-tree
MarkHard1238 小时前
Leetcode (力扣)做题记录 hot100(62,64,287,108)
算法·leetcode·职场和发展
一只鱼^_9 小时前
牛客练习赛138(首篇万字题解???)
数据结构·c++·算法·贪心算法·动态规划·广度优先·图搜索算法
一只码代码的章鱼9 小时前
Spring的 @Validate注解详细分析
前端·spring boot·算法