百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

数组-一文搞定前缀和数组(双数组前缀树)

wptr33 2025-03-29 23:04 19 浏览

前言

就从数组开始,以后会一直更新算法。数组有下图这些知识点与技巧。本文主要讲解其中的前缀和知识点。

思路

适合的场景:原始数组不会被修改,且频繁查询某个区间的累加和。 创建一个prefixSum数组,长度比原数组nums长度多1。prefixSum[i]存储nums[0]到nums[i]的和。 尤其要注意prefixSum与nums的坐标换算,如下图所示。

区域和检索 - 数组不可变(一维前缀和)

leetcode第303题

解题思路
常规思路是通过遍历i到j。但这样时间复杂度就是O(n)。 采用前缀和。
sumRange = prefixSum[right + 1] - prefixSum[left]。注意原数组与前缀和数组的下标换算,例如nums[i]的前缀和是preSum[i + 1]。如下图所示。

复杂度分析
时间复杂度:初始化O(n),每次检索O(1),n是数组长度。 空间复杂度:O(n)。
代码

class NumArray {
    private int[] prefixSum;
    public NumArray(int[] nums) {
        prefixSum = new int[nums.length + 1];
        for (int i = 0; i < nums.length; i++) {
            prefixSum[i + 1] = prefixSum[i] + nums[i];
        }
    }
    public int sumRange(int left, int right) {
        return prefixSum[right + 1] - prefixSum[left];
    }
}

二维区域和检索 - 矩阵不可变(二维前缀和)

leetcode第304题

解题思路
题中要求值设为下图中的红框部分,则有下图红框部分和 = 下图蓝框部分的和 - 下图绿框部分的和 - 下图黄框部分的和 + 下图灰框部分的和。

image.png

定义原二维数组的前缀和数组为preSum。则preSum[i][j]表示原数组中(0, 0)坐标与(i - 1, j - 1)坐标组成的矩形区域的和,如下图所示,紫色框对应的前缀和为17。 这里需要注意原数组与前缀和数组的坐标换算。比如原数组坐标为(i - 1, j - 1),则在前缀和数组中的坐标为(i, j)。 而上面一开始提到的蓝框的和,绿框的和,黄框的和,灰框的和的求法都一样。也就是对应位置二维数组的前缀和。

所以上图中紫色部分前缀和又如何求呢?答案:上图紫色部分前缀和 = 下图黄色部分前缀和 + 下图红色部分前缀和 - 重叠部分前缀和 + 原数组(i - 1, j - 1)位置的值 ,即preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1] + matrix[i - 1][j - 1] = 9 + 14 - 8 + 2 = 17

所以只要求出原数组中每个位置的前缀和,就解决了本题。 复杂度分析
时间复杂度:初始化O(rc),每次检索O(1),其中r与c分别为matrix的行数和列数。 空间复杂度:O(rc)。
代码

class NumMatrix {
    private int[][] preSum;

    public NumMatrix(int[][] matrix) {
        int r = matrix.length;
        if (r == 0) {
            return;
        }
        int c = matrix[0].length;
        if (c == 0) {
            return;
        }
        preSum = new int[r + 1][c + 1];
        for (int i = 1; i <= r; i++) {
            for (int j = 1; j <= c; j++) {
                preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1] + matrix[i - 1][j - 1];
            }
        }
    }

    public int sumRegion(int row1, int col1, int row2, int col2) {
        return preSum[row2 + 1][col2 + 1] - preSum[row1][col2 + 1] - preSum[row2 + 1][col1] + preSum[row1][col1];
    }
}

和为 K 的子数组

leetcode第560题

解题思路
思路
常规思路是通过双重循环遍历前缀和数组,j < i,若当
preSum[j] + k == preSum[i]时,说明数组从j - 1到i的和为k,则count++。但这样时间复杂度就是O(n^2)。 采用HashMap + 前缀和数组方式,时间复杂度可达到O(n)。 由preSum[j] + k == preSum[i]移项得preSum[j] == preSum[i] - k。所以只要统计有多少个前缀和为preSum[i] - k即可以统计出有多少个子串的和为k。 建立map,其中key=preSum[i],value=满足preSum[i] - k的preSum[j]有多少个(有多少个满足,就代表有多少个子串的和为k),其中必须j < i。
示例
示例nums = [1,2,3],k = 3。流程如下。 1.由于不会事先构建前缀和数组,所以此处先添加前缀和的第0个元素。如下图所示。

2.从nums的第0项(对应前缀和数组的第1项),开始遍历。此时preSum - k = -2。map中不存在key = -2的键值对。所以此时count = 0。并将key = preSum = 1, value = 1加入map。如下图所示。

3.访问nums数组的第1项(对应前缀和数组的第2项)。此时preSum - k = 0。map中存在key =0的键值对,且value = 1。所以此时count = count + value = 1。并将key = preSum = 3, value = 1加入map。如下图所示。

4.访问nums数组的第2项(对应前缀和数组的第3项)。此时preSum - k = 3。map中存在key = 3的键值对,且value = 1。所以此时count = count + value = 2。并将key = preSum = 6, value = 1加入map。如下图所示。

复杂度分析
时间复杂度:O(n),其中n为数组的长度 空间复杂度:O(n),哈希表在最坏情况下可能有n个不同的键值,因此为O(n)。
代码

class Solution {
   public int subarraySum3(int[] nums, int k) {
      HashMap map = new HashMap<>();
      //初始情况,由于此处没有使用前缀和数组,因此需要先将前缀和为0的,出现了1次的的情况记录在map里面,也就是前缀数组中的第0项
      map.put(0, 1);
      int count = 0, sum0i = 0;
      for (int i = 0; i < nums.length; i++) {
         sum0i += nums[i];
         count += map.getOrDefault(sum0i - k, 0);
         //以下两句代码,必须在以上两句代码的后边,这样变相保证了j < i
         int c = map.getOrDefault(sum0i, 0);
         map.put(sum0i, ++c);
      }
      return count;
   }
}

结尾

好了数组中的前缀和技巧就讲这三道。下一篇算法文章讲差分数组。

微信扫描下方二维码,关注公众号后回复【笔记】,有我准备的15万字Java面试笔记。

感谢各位人才的点赞、收藏和评论,干货文章持续更新中,下篇文章再见!

相关推荐

什么是Java中的继承?如何实现继承?

什么是继承?...

Java 继承与多态:从基础到实战的深度解析

在面向对象编程(OOP)的三大支柱中,继承与多态是构建灵活、可复用代码的核心。无论是日常开发还是框架设计,这两个概念都扮演着至关重要的角色。本文将从基础概念出发,结合实例与图解,带你彻底搞懂Java...

Java基础教程:Java继承概述_java的继承

继承概述假如我们要定义如下类:学生类,老师类和工人类,分析如下。学生类属性:姓名,年龄行为:吃饭,睡觉老师类属性:姓名,年龄,薪水行为:吃饭,睡觉,教书班主任属性:姓名,年龄,薪水行为:吃饭,睡觉,管...

java4个技巧:从继承和覆盖,到最终的类和方法

日复一日,我们编写的大多数Java只使用了该语言全套功能的一小部分。我们实例化的每个流以及我们在实例变量前面加上的每个@Autowired注解都足以完成我们的大部分目标。然而,有些时候,我们必须求助于...

java:举例说明继承的概念_java继承的理解

在现实生活中,继承一般指的是子女继承父辈的财产。在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物,同理,...

从零开始构建一款开源的 Vibe Coding 产品 Week1Day4:业界调研之 Agent 横向对比

前情回顾前面两天我们重点调研了了一下Cursor的原理和Cursor中一个关键的工具edit_file的实现,但是其他CodingAgent也需要稍微摸一下底,看看有没有优秀之处,下...

学会这几个插件,让你的Notepad++使用起来更丝滑

搞程序开发的小伙伴相信对Notepad++都不会陌生,是一个占用空间少、打开启动快的文件编辑器,很多程序员喜欢使用Notepad++进行纯文本编辑或者脚本开发,但是Notepad++的功能绝不止于此,...

将 node_modules 目录放入 Git 仓库的优点

推荐一篇文章Whyyoushouldcheck-inyournodedependencies[1]...

再度加码AI编程,腾讯发布AI CLI并宣布CodeBuddy IDE开启公测

“再熬一年,90%的程序员可能再也用不着写for循环。”凌晨两点半,王工还在公司敲键盘。他手里那份需求文档写了足足六页,产品经理反复改了三次。放在过去,光数据库建表、接口对接、单元测试就得写两三天。现...

git 如何查看stash的内容_git查看ssh key

1.查看Stash列表首先,使用gitstashlist查看所有已保存的stash:...

6万星+ Git命令懒人必备!lazygit 终端UI神器,效率翻倍超顺手!

项目概览lazygit是一个基于终端的Git命令可视化工具,通过简易的TUI(文本用户界面)提升Git操作效率。开发者无需记忆复杂命令,即可完成分支管理、提交、合并等操作。...

《Gemini CLI 实战系列》(一)Gemini CLI 入门:AI 上命令行的第一步

谷歌的Gemini模型最近热度很高,而它的...

deepin IDE新版发布:支持玲珑构建、增强AI智能化

IT之家8月7日消息,深度操作系统官方公众号昨日(8月6日)发布博文,更新推出新版deepin集成开发环境(IDE),重点支持玲珑构建。支持玲珑构建deepinIDE在本次重磅更...

狂揽82.7k的star,这款开源可视化神器,轻松创建流程图和图表

再不用Mermaid,你的技术文档可能已经在悄悄“腐烂”——图表版本对不上、同事改完没同步、评审会上被一句“这图哪来的”问得哑口无言。这不是危言耸听。GitHub2025年开发者报告显示,63%的新仓...

《Gemini CLI 实战系列》(五)打造专属命令行工具箱

在前几篇文章中,我们介绍了GeminiCLI的基础用法、效率提升、文件处理和与外部工具结合。今天我们进入第五篇...