算法【Java】 —— 前缀和

news2025/1/1 22:01:51

模板引入

一维前缀和

https://www.nowcoder.com/share/jump/9257752291725692504394

在这里插入图片描述


解法一:暴力枚举

在每次提供 l 与 r 的时候,都从 l 开始遍历数组,直到遇到 r 停止,这个方法的时间复杂度为 O(N * q)

解法二:前缀和

如果我们事先就知道所有从 0 开始的子数组的前缀和的话,那么要计算 从 l 到 r 之间的字数组的和的时候,我们就可以利用这个已知的前缀和数组进行计算,也就是 ans = dp[r] - dp[l] + nums[l]

时间复杂度为 O(N + q) , 空间复杂度为 O(N)

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();
        int q = in.nextInt();

        long[] arr = new long[n];

        for(int i = 0; i < n; i++) {
            arr[i] = in.nextInt();
        }

        //构建前缀和数组
        long[] dp = new long[n+1];
        for(int i = 1; i <= n; i++) {
            dp[i] = dp[i-1] + arr[i-1];
        }

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

二维前缀和

https://www.nowcoder.com/share/jump/9257752291725693083065

在这里插入图片描述
在这里插入图片描述


解法一:暴力枚举

直接遍历矩阵,时间复杂度为 O(N² * q)

解法二:前缀和

和上面那一道题目一样,我们先预处理一个前缀和数组,只不过这次是一个二维数组,我们需要讨论一下:

在这里插入图片描述

由上图可知,要想求 dp[i][j],我们可以利用上面的关系来推导,也就是 dp[i][j] 等于三块颜色的区域 加上 原矩阵对应的 nums[i][j]

dp[i-1][j] 等于蓝色区域加上绿色区域,dp[i][j-1] 等于蓝色区域加上橙色区域,dp[i-1][j-1] 等于蓝色区域

dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + nums[i][j]

但是要注意可能会出现数组越界访问的情况,因为当 i = 0 或者 j = 0 的时候 ,dp[i][j-1] 是会发生越界访问的,所以要避免发生越界访问的话,我们可以加多一个外围区域,就是将 dp 数组 在创建的时候,可以将 dp[][] = new int[n+1][m+1],也就是比原先数组多一行和多一列

这样的话,我们在进行 dp 数组计算的时候,下标就应该从 1 开始,对应的nums 数组的下标则是减一即可。

dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + nums[i-1][j-1]

如何获取从 (x1,y1) 到 (x2,y2) 之间的区域的和

在这里插入图片描述
因为正好是x1 ,y1, x2, y2 可以对应我们的 dp 数组,所以直接使用

蓝色区域是我们要求的区域,蓝色区域 等于 = dp[x2][y2] - (dp[x1-1][y2] + dp[x2][y1-1] - dp[x1-1][y1-1]

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();
        int m = in.nextInt();
        int q = in.nextInt();
        int[][] arr = new int[n][m];
        for(int i = 0; i < n; i++) {
            for(int j = 0; 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][j-1] + dp[i-1][j] - dp[i-1][j-1] + arr[i-1][j-1];
            }
        }

        while(q > 0) {
            int x1 = in.nextInt();
            int y1 = in.nextInt();
            int x2 = in.nextInt();
            int y2 = in.nextInt();
            System.out.println(dp[x2][y2] - (dp[x1-1][y2] + dp[x2][y1-1] - dp[x1-1][y1-1]));
            q--;
        }
    }
}

小结

前缀和主要用于处理数组区间的和的问题。

前缀和处理二维数组(矩阵)的时候,要注意 dp 数组要扩大。

实战演练

寻找数组的中心下标

https://leetcode.cn/problems/find-pivot-index/description/

在这里插入图片描述

解析:
中心下标表示除了这个下标对应的数字之外,其左边区域和等于右边区域和

区域和,我们可以联想到前缀和来解决,由于需要比较左右两边的区域和,我们可以设置前缀和数组与后缀和数组。

注意事项
前缀和应该从 下标 1 开始计算,f[i] = f[i-1] + nums[i-1]
后缀和应该从下标 n - 2 开始计算,g[i] = g[i+1] + nums[i+1]
因为上面两条公式需要利用到之前的前缀和或者后缀和,如果前缀和从0开始计算,则会数组越界访问异常,并且题目告诉我们如果中心下标是最左端,则左侧数组和为 0 ,右侧也一样,所以我们可以直接从 0 或者 n - 2 开始计算。

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;
    }
}

除自身以外数组的乘积

https://leetcode.cn/problems/product-of-array-except-self/description/

在这里插入图片描述

解析:
和上面的题目类似,这次是乘法,我们可以使用前缀积和后缀积来解答

这里要注意 f[0] 和 g[n-1] 要先预设置为 0,避免后续计算的时候得到的全是 0

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] f = new int[n];
        int[] g = 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];
        }

        int[] ans = new int[n];
        for(int i = 0; i < n; i++) {
            ans[i] = f[i] * g[i];
        }

        return ans;
    }
}

和为 K 的子数组

https://leetcode.cn/problems/subarray-sum-equals-k/description/

在这里插入图片描述


注意这里不能使用滑动窗口来解决!!!

滑动窗口有一个特点就是右指针不能进行回退,所以当你要使用滑动窗口来解决子数组的时候,要保证一个前提:数组具有单调性。

而这道题目由于数组可能会出现负数,也就是说明数组是不具有单调性的,举个例子:
在这里插入图片描述

两块蓝色区域相加正好等于零,而中间红色区域等于K 的时候,你使用滑动窗口,由于右指针不能回退,导致遗漏掉中间红色数组的情况,所以,我们不能使用滑动窗口。


解法一:暴力枚举

通过两层 循环,获取所有的子数组,时间复杂度为 O(N²)

解法二:前缀和加哈希表

计算 [0, i] 区间的前缀和 sum[i]

我们把要求的 K 子数组定义为以 i 结尾的子数组

如果存在 K 的子数组的话:
在这里插入图片描述

那么如果 i 数组前面出现过 子数组和为 sum[j] 的话,就说明存在 K 数组,sum[j] = sum[i] - K

我们在计算 前缀和 sum[i] 的时候,其实也就已经得知 i 数组前面所有的下标的前缀和,那么我们可以利用哈希表来保存这些数据,哈希表存储的应该是 <sum[i], 出现的次数>

我们其实不需要创建前缀和数组
因为我们只是要利用前一个前缀和结果来推导现在这一个前缀和,对于前几次的前缀和结果我们其实用不到,所以我们可以使用一个变量 sum,一直滚动累加下去即可。

要事先将 <0,1> 存进哈希表中
因为可能会出现一开始就得到 和为 K 的数组,此时 sum - k = 0,但是 count 却没有自增,所以我们事先设定存在一个前缀和 为 0 的子数组,并且出现次数为 1

class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0,1);
        int sum = 0;
        int count = 0;
        for(int x : nums) {
            sum += x;
            if(map.containsKey(sum - k)) {
                count += map.get(sum-k);
            }
            map.put(sum,map.getOrDefault(sum,0) + 1);
        }
        return count;
    }
}

和可被 K 整除的子数组

https://leetcode.cn/problems/subarray-sums-divisible-by-k/description/

在这里插入图片描述


解析:

这里依旧不能使用滑动窗口来做
因为存在负数,不具有单调性。

补充知识:同余定理,当 (a - b) % p = 0 可以推导出 a % p = b % p,有了这个公式,我们就可以得出当存在另外几个 b 满足 a % p = b % p 的时候,则说明存在 和可被 K 整除的子数组,在判断的时候,我们用到的是余数,所以在哈希表中,我们保存的应该为 <余数,余数出现的次数>,后面就是基本的前缀和操作了,和上面那一道题目类似。

Java 当中,负数 % 正数 的结果为 负数,我们需要将结果纠正为正数,公式为 ret = (sum % k + k) % k
解释一下,sum % k 可以得到余数,但是可能为负数,所以再加 一个 k,保证这是一个正数,但是可能会破坏余数这个特性,所以还要再次 模 k,最后的 模k 是不会影响的。

由于可能会出现一开始前缀和 正好为 K ,取模结果为0,此时应该预先将 <0,1> 存储进哈希表里。

class Solution {
    public int subarraysDivByK(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0,1);
        int sum = 0;
        int count = 0;
        for(int x : nums) {
            sum += x;
            int ret = (sum % k + k) % k;
            count += map.getOrDefault(ret, 0);
            map.put(ret,map.getOrDefault(ret,0) + 1);
        }
        return count;
    }
}

连续数组

https://leetcode.cn/problems/contiguous-array/description/

在这里插入图片描述

解析:

由于数组只会出现 0 或者 1,我们要寻找最大长度的数组(满足 0 的数量等于 1 的数量),如果要使用前缀和,我们可以将 0 设置为 -1,这样,我们要寻找的子数组(记为 K)的和就是为 0

前缀和数组sum[i] :

在这里插入图片描述

此时 K = 0,那么 就是要得到目前是否存在 sum[j] 数组是等于 sun[i],如果存在,则需要计算这个 K 数组的区间长度 = i - j

于是我们可以将前缀和 与 对应的下标索引 存到哈希表里,<sum,下标>

我们不用真的创建一个前缀和数组,因为我们用不到,我们可以使用一个变量 sum 来保存当前的前缀和结果

如果当整个数组的和为 K 的时候,那么最大的长度应该为 整个数组的长度,此时 i 最大到达 nums.length - 1 ,要想获得 数组长度,我们应该预先将 <0, -1> 存储进哈希表中

哈希表的 put :
我们保存数据的时候,要保存的是 sum 对应的最小的索引,因为我们要求的是最大的数组长度,所以当哈希表存在 sum[i] 的时候,我们应该更新最新长度,这个时候,就不用保存进哈希表里了,因为我们不需要跟新下标索引值,如果没有出现的话,那么此时 sum[i] 所对应的下标要一起保存进哈希表中。

class Solution {
    public int findMaxLength(int[] nums) {
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0,-1);
        int n = nums.length;
        int sum = 0;
        int maxLength = 0;
        for(int i = 0; i < n; i++) {
            sum += (nums[i] == 0 ? -1 : 1);
            if(map.containsKey(sum)) {
                int j = map.get(sum);
                maxLength = Math.max(maxLength,i - j);
            } else {
                map.put(sum,i);
            }
        }
        return maxLength;
    }
}

矩阵区域和

https://leetcode.cn/problems/matrix-block-sum/description/

在这里插入图片描述

解析:

这道题目就是二维数组前缀和的使用

我们先创建好前缀和数组,然后锁定求解的区域,最后计算即可

这里要注意的是前缀和数组要和我们的最终数组要一一对应

class Solution {
    public int[][] matrixBlockSum(int[][] mat, int k) {
        int n = mat.length;
        int m = mat[0].length;
        int[][] dp = new int[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] - dp[i-1][j-1] + mat[i-1][j-1];
            }
        }

        int[][] ans = new int[n][m];
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                int r1 = (i - k <= 0 ? 1 : i - k + 1);
                int c1 = (j - k <= 0 ? 1 : j - k + 1);
                int r2 = (i + k >= n ? n : i + k + 1);
                int c2 = (j + k >= m ? m : j + k + 1);
                ans[i][j] = dp[r2][c2] - (dp[r1-1][c2] + dp[r2][c1-1] - dp[r1-1][c1-1]);
            }
        }
        return ans;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2113015.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不会Excel怎么制作桑基图?用什么软件绘制比较好呢?推荐2款简单好用的图表制作工具

桑基图制作很简单&#xff0c;不需要任何基础一次就会&#xff01; 2个桑基图制作工具&#xff0c;帮你一键解决问题~ 1、Dycharts 推荐指数&#xff1a;☆☆☆☆☆ 点击链接直达>>dycharts.com Dycharts是国内一款专业的在线图表制作工具&#xff0c;0代码、无门槛&…

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(2) &#xff08;*****生成数据结构类的方式特别有趣****&a…

AI基础 L5 Uninformed Search II 无信息搜索

How good is search? • Completeness: Does it always find a solution if one exists? 是否通解 • Optimality: Is the solution optimal (i.e., lowest cost)? 是否最优 • Time Complexity: How long does it take to find a solution? 时间多久 • Space Complexit…

程序员都在使用的画图工具

大家好&#xff0c;我是袁庭新。 程序员都在使用的画图工具&#xff0c;你一定没用过这款画图工具吧&#xff01;我教程中的架构图都是用它来画的。 比如我编写的RDB工作原理图就是用draw.io绘制的&#xff0c;如下图所示&#xff1a; 再例如Redis集群故障恢复原理图我也是通…

【论文阅读】DETRs Beat YOLOs on Real-time Object Detection

文章目录 摘要一、介绍二、相关工作2.1 实时目标检测器2.2 端到端目标检测器 三、检测器的端到端速度3.1 分析 NMS3.2 端到端速度基准 四、实时 DETR4.1 模型概述4.2 高效混合编码器4.3不确定性最小的查询选择4.4 缩放的RT - DETR 五、实验5.1 与SOTA对比5.2 混合编码器的消融研…

VMware Fusion Pro 13 for Mac虚拟机软件

Mac分享吧 文章目录 效果一、下载软件二、开始安装安装完成&#xff01;&#xff01;&#xff01; 效果 一、下载软件 下载软件 地址&#xff1a;www.macfxb.cn 二、开始安装 安装完成&#xff01;&#xff01;&#xff01;

F12抓包06-4:导出metersphere脚本

metersphere是一站式的开源持续测试平台&#xff0c;我们可以将浏览器请求导出为HAR文件&#xff0c;导入到metersphere&#xff0c;生成接口测试。 metersphere有2种导入入口&#xff08;方式&#xff09;&#xff0c;导入结果不同&#xff1a; 1.导入到“接口定义”&#xf…

ctfshow-web入门-sql注入(web237-web240)insert 注入

目录 1、web237 2、web238 3、web239 4、web240 1、web237 查询语句&#xff1a; //插入数据$sql "insert into ctfshow_user(username,pass) value({$username},{$password});"; 我们需要闭合单引号和括号 添加&#xff0c;查数据库名&#xff0c;payload&…

想要从OPPO手机恢复数据?免费OPPO照片视频恢复软件

此实用程序可帮助那些寻找以下内容的用户&#xff1a; 在OPPO手机中格式化存储卡后可以恢复图片吗&#xff1f;我删除了 OPPO上的视频和图片&#xff0c;我感觉很糟糕&#xff0c;因为里面有我在拉斯维加斯拍摄的视频和照片 免费OPPO照片视频恢复软件 您能恢复OPPO上已删除的…

解锁2024年PDF转PPT新技能,TOP4神器在手,职场晋升竟然如此简单

如今职场节奏快&#xff0c;信息传递和展示方式多样。PDF 兼容性强且稳定&#xff0c;用于分享和保存文件&#xff1b;PPT 演示功能强大&#xff0c;在开会、教学和汇报中不可或缺。实际工作中常需将 PDF 转 PPT&#xff0c;以便更好地演示和编辑。市场上因此出现众多高效方便的…

使用NetBackup GUI 图形化进行oracle备份和恢复

转载 一、环境介绍&#xff1a; 这个实验都是在vmware workstation里完成的。由于NetBackup7只能装在64位的系统上&#xff0c;所以这里采用了64位的rhel5.5系统&#xff0c;以及oracle 10gr2 for linux_x64的软件包。数据库的数据文件存储在ASM中。安装rhel、oracle、netback…

Selenium 实现图片验证码识别

前言 在测试过程中&#xff0c;有的时候登录需要输入图片验证码。这时候使用Selenium进行自动化测试&#xff0c;怎么做图片验证码识别&#xff1f;本篇内容主要介绍使用Selenium、BufferedImage、Tesseract进行图片 验证码识别。 环境准备 jdk&#xff1a;1.8 tessdata&…

关于CPP——std::future异步操作

目录 一、std::future 简介 1.1 概念 1.2 应用场景 1.3 关联的方法 1.3.1 std::async 1.3.2 std::package 1.3.3 std::promise 二、future 用法 2.1 使用std::async关联异步任务 2.2 使用std::packaged_task 1. 获取任务结果的机制&#xff1a; 2. 异步任务的管理&a…

jmeter之仅一次控制器

仅一次控制器作用&#xff1a; 不管线程组设置多少次循环&#xff0c;它下面的组件都只会执行一次 Tips&#xff1a;很多情况下需要登录才能访问其他接口&#xff0c;比如&#xff1a;商品列表、添加商品到购物车、购物车列表等&#xff0c;在多场景下&#xff0c;登录只需要…

【STM32 Blue Pill编程】-ADC数据采样(轮询、中断和DMA模式)

ADC数据采样(轮询、中断和DMA模式) 文章目录 ADC数据采样(轮询、中断和DMA模式)1、硬件准备及接线2、ADC轮询模式2.1 轮询模式配置2.2 代码实现3、ADC中断模式3.1 中断模式配置3.2 代码实现4、ADC的DMA模式4.1 DMA模式配置4.2 代码实现在本文中,我们将介绍如何使用 ADC 并…

[JAVA基础知识汇总-1] 创建线程的几种方式

文章目录 1. 继承Thread类2. 实现Runnable接口3. 实现Callable接口4. 线程池 可以认为有四种方式&#xff0c;也可以认为有一种&#xff0c;因为都跟Runnable接口有关 1. 继承Thread类 代码 public class Thread1ExtendsThread extends Thread { // public Thread1(String …

思维导图与头脑风暴:你值得拥有的四大工作与学习利器

在工作与学习中&#xff0c;我们都遇到过这样的情况&#xff1a;我们需要就某一问题或项目&#xff0c;汇集多人的智慧与创意&#xff0c;这时&#xff0c;头脑风暴便成为了我们不可或缺的利器&#xff1b;而为了更好地进行头脑风暴&#xff0c;选择一款合适的在线思维导图工具…

【Qt开发】QT6.5.3安装方法(使用国内源)亲测可行!!!

目录 &#x1f315;下载在线安装包&#x1f315; 把安装包放到系统盘&#x1f315;开始安装&#x1f315;参考文章 &#x1f315;下载在线安装包 https://mirrors.nju.edu.cn/qt/official_releases/online_installers/ &#x1f315; 把安装包放到系统盘 我的系统盘是G盘&…

uniapp 全屏日历,动态无限加载

不好用请移至评论区揍我 原创代码,请勿转载,谢谢! 注:本人仅在微信小程序测试过,未在其他app/h5尝试过,按理说应该是可以的,代码没有引用任何第三方组件 日历中每个日期下方的空白部分均可自定义,写在代码中的<view class="item">我是内容</view>…

Go入门指南(The Way to Go) 完整版PDF

The Way To Go可以说是入门 Go 的经典书籍&#xff0c;这本书有内容丰富各种资料链接&#xff0c;这是截止到目前&#xff0c;大叔看到的写得最好的go 语言教材&#xff0c;非常详细.一口气读下来&#xff0c;舍不得放手&#xff0c;大叔强烈推荐你去学习 百度网盘分享