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

前缀和算法:解锁高效编程的钥匙

1.【模板】一维前缀和

题目要求

题目分析: 

 

算法分析:

 

前缀和算法:快速求出数组中某一段连续区间的和。 

 

 

 代码步骤:

(1)输入数据

  • 从标准输入读取两个整数 n 和 q

    • n 表示数组的长度。

    • q 表示查询的次数。

  • 读取数组 arr,注意数组下标从 1 开始。

(2)前缀和数组

  • 创建一个前缀和数组 prefix_sum,其中 prefix_sum[i] 表示数组 arr 中前 i 个元素的和。

  • 前缀和的计算公式:

    prefix_sum[i] = prefix_sum[i - 1] + arr[i];

(3)查询处理

  • 对于每个查询,输入区间的左右边界 l 和 r

  •  使用前缀和数组快速计算区间和:
prefix_sum[r] - prefix_sum[l - 1]

细节补充:设置前缀和数组时,定义第一个元素的下标为1,不是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[] prefix_sum = new long[n + 1];
        for(int i = 1;i <= n;i++) prefix_sum[i] = prefix_sum[i - 1] + arr[i];

        //3.使用前缀和数组

        while(q > 0){
            int l = in.nextInt(), r = in.nextInt();
            System.out.println(prefix_sum[r] - prefix_sum[l - 1]);
            q--;
        }
    }
}

代码通过:


        2.【模板】二维前缀和

题目要求:

题目解析:

 原理讲解:

 

源代码:

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        //读入数据
        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--;
        }
    }
}

 代码解析:

(1)输入数据

  • 从标准输入读取三个整数 nm 和 q

    • n 表示二维数组的行数。

    • m 表示二维数组的列数。

    • q 表示查询的次数。

  • 读取二维数组 arr,注意数组下标从 1 开始。

(2)二维前缀和数组

  • 创建一个二维前缀和数组 dp,其中 dp[i][j] 表示从 arr[1][1] 到 arr[i][j] 的子矩阵的和。

  • 前缀和的计算公式:

    dp[i][j] = dp[i-1][j] + dp[i][j-1] + arr[i][j] - dp[i-1][j-1];

(3)查询处理

  • 对于每个查询,输入子矩阵的左上角 (x1, y1) 和右下角 (x2, y2)

  • 使用前缀和数组快速计算子矩阵和:

    dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1]

代码通过:


3. 寻找数组的中心下标

题目描述: 

 算法原理:

 根据题目要求,如果要求当前下标 i,则

  • 左边要判断的是0 ~ i-1 元素的和
  • 右边要判断的是n-1 ~ i+1元素的和

使用前缀和思想 

预处理两个数组,分别存放i下标左边和右边元素之和。

注意:不要加上i下标的元素(别跟前面的前缀和题搞混)

第一步: 

f 数组存放前缀和 :

 g数组存放后缀和:

 第二步:

注意细节:

  • f(0)表示nums[ 0 ]位置左边默认为0(因为没有元素),同时处理边界问题,g[ n - 1 ]亦如此。
  • g数组存放后缀和,存放顺序记得从右向左,为了和f数组找对应下标 

 

第三步: 

 只需用循环同时遍历f数组和g数组即可如果f[ i ] == g[ i ] ,那就是该下标

 举例:

源码:

class Solution {
    public int pivotIndex(int[] nums) {

        int n = nums.length;
        int[] f = new int[n];
        int[] g = new int[n];

        //预处理两个数组
        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++){
            if(f[i] == g[i]) return i;
        }
        return -1;
        
    }
}

给定一个整数数组 nums,找到一个下标 i,使得:

  • 下标 i 左边的元素之和等于右边的元素之和。

  • 如果不存在这样的下标,返回 -1

  1. 预处理两个数组

    • f[i]:表示下标 i 左边元素的和(不包括 nums[i])。

    • g[i]:表示下标 i 右边元素的和(不包括 nums[i])。

  2. 计算 f 和 g

    • f[i] = f[i - 1] + nums[i - 1]:从左到右累加。

    • g[i] = g[i + 1] + nums[i + 1]:从右到左累加。

  3. 寻找中心下标

    • 遍历数组,找到满足 f[i] == g[i] 的下标 i

    • 如果找不到,返回 -1


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

算法原理:

和上一道题类似,也是除了本身元素,计算左边和右边的积

第一步: 

 

 第二步:

注意细节:

f [ 0 ] 此时要变成 1,不然相乘全为0,g[n - 1] 同理; 

第三步:

新创一个数组,遍历存放即可;

 比如:

 源码:

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;

        int[] f = new int[n];
        int[] g = new int[n];

        f[0] = 1;
        for(int i = 1;i<n;i++){
            f[i] = f[i - 1] * nums[i - 1];
        }

        g[n-1] = 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;
        
    }
}
  1. 第一次遍历(使用数组f)计算从左到右的累积乘积。f[i]存储的是nums[0]nums[i-1]的乘积(注意f[0]被初始化为1,因为没有元素在nums[0]的左边)。

  2. 第二次遍历(使用数组g)计算从右到左的累积乘积。g[i]存储的是nums[i+1]nums[n-1]的乘积(注意g[n-1]被初始化为1,因为没有元素在nums[n-1]的右边)。

  3. 最后,通过遍历数组nums,用f[i]g[i]的乘积来填充结果数组ret,这样ret[i]就包含了除了nums[i]以外的所有元素的乘积。

 


5.和为 K 的子数组

题目要求: 

 算法原理:前缀和 + 哈希表(记住一段区间元素之和,其实等于前缀和的一个数字)

第一步:

第二步: 

使用哈希表存放前缀和出现的次数。

  

第三步: 

 

 源代码:

class Solution {
    public int subarraySum(int[] nums, int k) {
        // 哈希表,记录前缀和及其出现次数
        Map<Integer, Integer> hash = new HashMap<>();
        hash.put(0, 1); // 前缀和为 0 的情况出现了 1 次

        int sum = 0; // 当前前缀和
        int ret = 0; // 结果:满足条件的子数组个数

        for (int x : nums) {
            sum += x; // 计算当前前缀和
            // 如果存在前缀和 sum - k,则说明从某个位置到当前位置的子数组和为 k
            ret += hash.getOrDefault(sum - k, 0);
            // 更新当前前缀和的出现次数
            hash.put(sum, hash.getOrDefault(sum, 0) + 1);
        }

        return ret; // 返回结果
    }
}


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

 题目要求:

 算法原理:前缀和 + 哈希表

前提引入两个结论

1.同余定理:如果(a - b) % k = 0 ,则a % k  ==  b % k;(记住结论,不证明)

举例:(26 - 12 ) %  7  = 0,所以 26 % 7 = 12 % 7 = 5;

2. 修正余数:

  • 负数 % 正数 = 负数;比如(a 是 负数 ,b是正数) 
  • a  % b  (负数)
  • a % b + b (变为正数,但是a和b都是正数,则余数会错)
  • (a % b  + b) % b(最终令正负统一)

 第一步:

第二步: 

使用哈希表存放前缀和的余数与出现的次数。

  

第三步:细节与上一题类似

源代码:

class Solution {
    public int subarraysDivByK(int[] nums, int k) {
        // 哈希表,记录前缀和对 k 取模的结果及其出现次数
        Map<Integer, Integer> has = new HashMap<>();
        has.put(0 % k, 1); // 前缀和对 k 取模为 0 的情况出现了 1 次

        int sum = 0; // 当前前缀和
        int ret = 0; // 结果:满足条件的子数组个数

        for (int x : nums) {
            sum += x; // 计算当前前缀和
            // 计算当前前缀和对 k 取模的结果,并处理负数情况
            int r = (sum % k + k) % k;
            // 如果存在前缀和对 k 取模的结果 r,则说明从某个位置到当前位置的子数组和能被 k 整除
            ret += has.getOrDefault(r, 0);
            // 更新当前前缀和对 k 取模的结果 r 的出现次数
            has.put(r, has.getOrDefault(r, 0) + 1);
        }

        return ret; // 返回结果
    }
}


7.连续数组 

题目要求: 

算法原理:前缀和 + 哈希表(哈希表存放的是前缀和 和 对应的下标)

第一步:

 

 第二步:

 源代码:

class Solution {
    public int findMaxLength(int[] nums) {
        // 哈希表,记录前缀和及其第一次出现的位置
        Map<Integer, Integer> hash = new HashMap<>();
        hash.put(0, -1); // 前缀和为 0 的情况出现在索引 -1

        int sum = 0; // 当前前缀和
        int ret = 0; // 结果:最长满足条件的子数组长度

        for (int i = 0; i < nums.length; i++) {
            // 将 0 视为 -1,1 视为 1
            sum += (nums[i] == 0 ? -1 : 1);
            // 如果哈希表中存在当前前缀和 sum,则更新结果
            if (hash.containsKey(sum)) {
                ret = Math.max(ret, i - hash.get(sum));
            } else {
                // 否则,将当前前缀和 sum 及其索引 i 存入哈希表
                hash.put(sum, i);
            }
        }

        return ret; // 返回结果
    }
}


相关文章:

  • Leetcode---83.删除排序链表中的重复元素
  • 谷歌GMS认证,安卓14版本的谷歌EDLA认证详细介绍,谷歌安卓14 EDLA认证有多少测试项?
  • 2025年软考报名费用是多少?全国费用汇总!
  • HDFS数据存储与数据管理
  • Facebook Instant Game:即时游戏的新时代
  • JVM 简单内存结构及例子
  • 某住宅小区地下车库安科瑞的新能源汽车充电桩的配电设计与应用方案 安科瑞 耿笠
  • 算法系列之回溯算法
  • 35. Spring Boot 2.1.3.RELEASE 应用监控【监控信息可视化】
  • Python - Python连接数据库
  • 十一、k8s安全机制
  • Java篇之继承
  • 防御保护-----第五章:状态检测和会话技术
  • deepseek-r1-centos-本地服务器配置方法
  • Sliding Window Attention(滑动窗口注意力)解析: Pytorch实现并结合全局注意力(Global Attention )
  • 【模块】 ASFF 模块
  • CONTACT 在 Ubuntu 系统中的安装与使用
  • vue:vite 代理服务器 server: proxy 配置
  • 反爬虫策略
  • 深度神经网络(DNN)编译器原理简介
  • 乐山网站建设/百度云在线登录
  • 俄语购物网站建设/简述搜索引擎的工作原理
  • 如何做网站运营呢/百度竞价怎么做开户需要多少钱
  • 福州网站建设 大公司/推广方案怎么写
  • 湖南网站建设费用/怎么样关键词优化
  • 网站链接太多怎么做网站地图/bt种子万能搜索神器