跳转到内容

静态区间和:两个前缀相减

约 4 分钟原样导入

← 前缀和专题目录

01 · 一次记账,多次查询

同一份数组不改,却经常有人问“这一段加起来是多少”。能不能不用每次重新遍历?

手动推演 · 边界错误实验 · Java 代码 · 自测

原数组 [2,−1,3],查询闭区间 [1,2]。下标从 0 开始,包含下标 1 和 2,答案是 −1+3=2。支持负数,区间必须非空。

本节静态例题始终使用这个样例。下方自定义输入只改变主演示及联动代码,错误实验与自测各有独立样例。

暴力方法从 l 走到 r,读一个数加一次。单次短区间很直接,但许多重叠查询会反复累加同样的元素。

先把“从最左边开始”的累计和记下来:P=[0,2,1,4]。其中 P[3]=4 包含前 3 项,P[1]=2 只包含前 1 项。

要的区间 [1,2] 在大段 [0,3) 内。大段减掉左边多出的 [0,1),就只剩 [1,3),也就是原数组闭区间 [1,2]:P[3]−P[1]=4−2=2。共同左段会抵消,正数、负数都成立。

P 的下标表示取了多少个元素,nums 的下标表示第几个位置。n 个元素有 n+1 个前缀边界,包括一个都不取的 P[0]。所以 P[i+1]=P[i]+nums[i]。

闭区间 [l,r] 含 nums[r],右前缀必须取到 P[r+1]。左前缀 P[l] 刚好排除 nums[l]。公式为 P[r+1]−P[l],不需要给从 0 开始的查询额外写分支。

手动推演:先构造,再读取与相减

Section titled “手动推演:先构造,再读取与相减”

前缀解法每步构造一格,然后分三个动作读取右前缀、读取左前缀、做差。逐项累加解法每步加一个区间内元素。首次进入暂停,前后步进恢复完整状态。

演示尚未初始化;如果此提示持续显示,脚本未能加载。文字与 Java 示例仍可阅读。

交互推演
前缀和实验室 · 亲手推演初始状态

已应用数据 等待初始化正确做法

原数组 nums · 格子上的数字是元素,下标从 0 开始

前缀数组 P / pre · 格子下标是取了多少个元素

P[t] 表示 [0,t) 的和。? 表示尚未算出,P[0]=0 是空前缀。

初始状态

点击下一步开始

区间和尚未计算
右侧前缀 / 当前累加0
左侧前缀 before未查询
状态详情 · 边界与查询依据

前缀位置与区间列表仅供教学;Java 频次表存次数,不存全部位置。表中键按数值升序排版,不代表 HashMap 的遍历顺序。

复杂度账本

记录本次推演的算法动作,不代表 JVM 指令或播放性能。

支持 1~12 个 -99~99 的整数、中英文逗号、一对方括号。需 0 ≤ l ≤ r < 数组长度。限制用于保证画面可读。

前缀已建好且数组未改变时,应用新区间会复用前缀;重置或切换解法则重新开始。

查看 Java 代码 · 当前动作语句高亮

高亮表示刚执行完成的语句,一步可以覆盖多行;最后一步也包含返回值。代码仅为正确解法,错误实验独立展示。

前缀解法

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

暴力解法

class Solution {    public long sumRange(int[] nums, int left, int right) {        long sum = 0L;        for (int i = left; i <= right; i++) {            sum += nums[i];        }        return sum;    }}

Java 的 pre 使用 long[]。先构造一次 NumArray,再多次调用 sumRange;调用者需保证 0 ≤ left ≤ right < nums.length。

前缀下标与原数组下标相差一位源于定义,不是凭空加 1。

本页是与 Java 示例对应的教学推演,不运行任意 Java。加载完成后可断网操作;刷新清空输入、步骤和自测答案。

完成构造后,仅改变 l、r 再应用:数组未变时会复用前缀,只演示三个查询动作。修改数组、重置或切换解法会重新开始。编辑中的数据不会提前进入图形或答案。

P[r+1] 与 P[l] 的公共部分恰好是 nums[0…l−1],相减后只留下 nums[l…r],不漏两端,也不多算区间外元素。

逐项查询长度为 m 的区间,读取并累加 m 项,时间 O(m)。前缀解法先花 O(n) 时间与 O(n) 空间建表,以后每次两次读取、一次相减,时间 O(1)。Q 次查询总体 O(n+Q)。对一次很短的查询,预处理未必划算;动画步数不代表性能倍数。

前缀构造期间未算出的格子显示 ?,不会用 0 冒充结果。原数组改动后必须重建受影响的前缀,本课只讨论静态查询。

错误实验:少一个 +1,会漏掉什么

Section titled “错误实验:少一个 +1,会漏掉什么”

独立固定样例 [5],闭区间 [0,0]。正确答案 5。故意把 P[r+1] 写成 P[r],得到 P[0]−P[0]=0,漏掉唯一的右端点。

演示尚未初始化;如果此提示持续显示,脚本未能加载。文字与 Java 示例仍可阅读。

独立实验
独立错误实验 · 固定输入初始状态

已应用数据 等待初始化正确做法

原数组 nums · 格子上的数字是元素,下标从 0 开始

前缀数组 P / pre · 格子下标是取了多少个元素

P[t] 表示 [0,t) 的和。? 表示尚未算出,P[0]=0 是空前缀。

初始状态

点击下一步开始

区间和尚未计算
右侧前缀 / 当前累加0
左侧前缀 before未查询
状态详情 · 边界与查询依据

前缀位置与区间列表仅供教学;Java 频次表存次数,不存全部位置。表中键按数值升序排版,不代表 HashMap 的遍历顺序。

复杂度账本

记录本次推演的算法动作,不代表 JVM 指令或播放性能。

实验不会改变主演示数据和步骤;进入实验会暂停主播放,返回后不会自动恢复。

查看联动 Java。long[] pre 只构造一次;sumRange 每次读取 whole 与 before 再相减。为了拆开观察,本课把模板里的一行相减写成三个语句,含义不变。

输入需满足 0≤left≤right<nums.length。Java 示例使用 long 防止累加溢出;显示的有限输入范围是教学限制,不是 long 的数值上限。

自测尚未初始化;可先用纸笔作答。

独立自测样例:[2, -1, 3]

3. 以下哪项说法正确?

试着不看动画,解释 [0,0] 与 [0,n−1] 分别应该读哪两个前缀。自测只反馈本轮答案,不建立掌握度评分或长期记录。

依据 S2 技巧 1 的 [2,−1,3] 样例、S1 统一坐标系、S3 §3.3 NumArray 整理。完整书目与适用范围修订见专题目录。插入、删除会重算后缀,不强行加入这节静态查询课。

下一课:和为 K 的子数组 →