LeetCode · Prefix Sum
完整推导 / 可提交代码 / 面试复盘
INTERVIEW PREPARATION / PYTHON 3

前缀和
把一段区间,变成两个边界。

用 3 道题建立一条完整思考路径:从前缀定义出发,理解单调性、历史状态计数,再把思想迁移到前后缀乘积。每种解法都附有可独立提交的代码。

单文件 · 无外部依赖 · 可反复复习
sum(left, right) = prefix[right] − prefix[left]
03指定题目 · 全部中等
04由浅入深的学习阶段
07完整 Python 3 解法
02可操作的算法演示

建议每题先读题意并尝试 10 分钟,再看推导;理解后关闭答案默写,最后用边界用例自测。题目掌握进度与文末能力自测分别保存。

先看学习路线

阶段学习内容要达到的目标
01 · 基础与模板前缀和定义、左闭右开、静态区间查询看到区间 [l,r) 能写出 P[r] − P[l]
02 · 单调区间应用209 · 长度最小的子数组先用前缀和 + 二分,再理解线性滑动窗口
03 · 历史状态计数560 · 和为 K 的子数组从双重枚举推导出前缀和 + 频次哈希表
04 · 前后缀分解238 · 除自身以外数组的乘积从两个数组优化为复用输出数组的常数额外空间

选题边界:手册严格包含这 3 道题。238 研究的是前缀乘积与后缀乘积,归入前缀思想的扩展。209 的线性窗口解法依赖正整数;560 允许负数,不能照搬。

第一阶段 · 基础知识与核心模板

01 / FOUNDATION

1. 前缀和到底存什么?

设数组长度为 n。统一使用长度为 n + 1 的前缀数组 P:P[i] 表示前 i 个元素的和,它包含下标 0 到 i − 1,不包含 nums[i]。P[0] = 0 表示尚未取任何元素。

P[0] = 0 ; P[i + 1] = P[i] + nums[i]

对于左闭右开区间 [l, r),P[r] 包含「左侧部分 + 目标区间」,P[l] 恰好是「左侧部分」。相减便留下目标区间。区间长度也是 r − l。

前缀和相减得到目标区间数组为2、3、1、2、4、3,区间下标2到4的和为7,等于前5项和12减前2项和5。nums231243012 = l345 = rP[5] = 12减去 P[2] = 5[2, 5) → 1 + 2 + 4 = 12 − 5 = 7
前缀下标是元素之间的边界。右端点 r 不属于目标区间。
表达方式区间元素求和公式长度
左闭右开 [l, r)nums[l] … nums[r − 1]P[r] − P[l]r − l
左右都闭 [l, r]nums[l] … nums[r]P[r + 1] − P[l]r − l + 1
空区间 [l, l)没有元素P[l] − P[l] = 00

动手验证 · 两个边界决定一段和

固定数组 [2, 3, 1, 2, 4, 3];切换左闭右开边界。若 l > r,会将 r 自动对齐到 l。

2. 把模板理解成状态约定

模式状态含义更新顺序 / 核心式对应场景
静态区间和P[i] = 前 i 个数的和初始化 P[0]=0 → 逐个累加 → P[r]−P[l]大量区间求和;209 的二分版本
前缀和 + 频次表freq[s] = 已扫描的前缀中和值 s 的数量freq={0:1} → 更新 running → 查询 running−k → 记录 running560:连续子数组的计数
正数窗口total = nums[left:right+1] 的和右端加入 → while 达标时记录 → 左端移出209:最短达标区间
前后缀分解left[i] 不含 i;right[i] 不含 ianswer[i] = left[i] × right[i]238:排除当前位置的聚合

一次构建前缀数组花费 O(n) 时间和 O(n) 空间,之后每次区间查询只需 O(1)。若 q 次查询都直接对切片求和,最坏需要 O(nq);前缀和将其降到 O(n + q)。数组发生修改后,原前缀数组不再有效,需要相应重建或改用支持更新的数据结构。

三个易混点:前缀数组有 n + 1 项;Python 的 nums[l:r] 与 [l,r) 一致,但切片会分配新列表;在每次查询里执行 sum(nums[l:r]) 不会得到 O(1) 时间。

自测:为什么 P[0] 必须存在?

它统一了从下标 0 开始的区间。例如 [0,3) 的和为 P[3]−P[0]。在 560 中,空前缀还能代表「子数组从数组开头开始」;它是合法左边界,但答案中的子数组仍必须非空。

第二阶段 · 正数区间与单调性

02 / MONOTONICITY

先做 209,把「区间和」与「单调性」联系起来:正数使前缀和严格递增,因此可以二分,也可以让左右边界只向前移动。

01LEETCODE 209 / MEDIUM长度最小的子数组

LeetCode 原题 ↗

难度:中等 | 核心知识点:前缀和、二分查找、滑动窗口、双指针

题意与约束

给定一个正整数 target 和正整数数组 nums,返回连续子数组中总和至少为 target 的最短长度。如果所有区间都无法达标,返回 0。注意是「大于等于」而非恰好等于。

原题约束:1 ≤ n ≤ 10⁵;1 ≤ nums[i] ≤ 10⁴;1 ≤ target ≤ 10⁹。正数条件是下面两种高效解法的前提。题意及约束见卡片顶部原题链接。

输入输出解释
target=7,nums=[2,3,1,2,4,3]2[4,3] 达标,没有单个元素达到 7
target=4,nums=[1,4,4]1任选一个 [4]
target=11,nums=[1,1,1,1,1,1,1,1]0总和 8,无法达标

解法一 · 前缀和 + 二分查找

暴力做法枚举左右边界,维护区间累加和,需要 O(n²)。先用前缀和把每个区间的计算变为相减,再利用正数形成的有序性,便能避免枚举全部右边界。

固定左边界 left,寻找最小的右边界 right,使 P[right] − P[left] ≥ target,等价于在 P 中查找第一个不小于 P[left] + target 的位置。因为 nums 中每一项为正,P 严格递增,bisect_left 可以完成这个 lower_bound 查询。

right = lower_bound(P, P[left] + target) ; 长度 = right − left
Python 3 · 前缀和 + 二分查找
from bisect import bisect_left
from typing import List


class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        n = len(nums)
        prefix = [0] * (n + 1)
        for i, x in enumerate(nums):
            prefix[i + 1] = prefix[i] + x

        best = n + 1
        for left in range(n):
            need = prefix[left] + target
            right = bisect_left(prefix, need, left + 1)
            if right <= n:
                best = min(best, right - left)

        return 0 if best == n + 1 else best
关键代码解释
  • prefix[i + 1] 把 nums[i] 纳入前缀,避免遗漏第一个元素。
  • bisect_left(prefix, need, left + 1) 从 left 后的边界开始查找,保证子数组非空。它返回的是前缀数组下标。
  • prefix 长度为 n + 1:查找失败会返回 n + 1;right = n 仍是合法边界,代表区间延伸至末尾。
  • best = n + 1 表示尚无解,最后统一转成 0。
示例推演 · 前缀数组 [0,2,5,6,8,12,15]
leftP[left] + 7首个合格 right区间 [left,right)长度
074(P[4]=8)[2,3,1,2]4
195(P[5]=12)[3,1,2,4]4
2125(P[5]=12)[1,2,4]3
3136(P[6]=15)[2,4,3]3
4156(P[6]=15)[4,3]2
5197(查找失败)不存在不更新

正确性:对于固定左端点,二分找到的首个合格右边界给出该左端点下的最短区间。对所有左端点取最小值,就是全局最短长度。

时间复杂度:O(n log n),n 次二分。空间复杂度:O(n),前缀数组。

解法二 · 滑动窗口(面试首选)

正数还保证了窗口的单调性:右端加入元素,总和变大;左端移出元素,总和变小。右端每走一步,只要当前窗口已达标,就先记录答案,再持续缩短左端,直到再次不达标。

循环使用闭区间 [left,right]。虽然与前缀数组的 [l,r) 写法不同,但含义是明确的:窗口长度为 right − left + 1,移出元素前先更新答案。

Python 3 · 线性滑动窗口
from typing import List


class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        n = len(nums)
        left = 0
        total = 0
        best = n + 1

        for right, x in enumerate(nums):
            total += x
            while total >= target:
                best = min(best, right - left + 1)
                total -= nums[left]
                left += 1

        return 0 if best == n + 1 else best
关键代码解释
  • while total >= target 保证一次右移后能尝试所有仍达标的更短窗口,不能换成单次 if。
  • total -= nums[left] 必须在 left 自增之前,移走的才是窗口旧左端点。
  • total 始终维护当前窗口的和,无需重复求和或创建切片。
示例推演 · target = 7
right / 加入加入后的 totalwhile 内记录与收缩结束时 left / total / best
0 / 22未达标0 / 2 / 未找到
1 / 35未达标0 / 5 / 未找到
2 / 16未达标0 / 6 / 未找到
3 / 28[0,3] 长 4 → 移出 21 / 6 / 4
4 / 410[1,4] 长 4 → 7;[2,4] 长 3 → 63 / 6 / 3
5 / 39[3,5] 长 3 → 7;[4,5] 长 2 → 35 / 3 / 2

正确性与循环不变量:每次记录时 total 就是 [left,right] 的和。每次 while 结束,窗口已不达标。被移出的旧左端点在当前 right 下已经考察过;将来 right 更大时,沿用它只会得到更长的窗口,所以可以永久丢弃。所有可能改善答案的窗口都会被覆盖。

时间复杂度:O(n),两个指针各移动至多 n 次。空间复杂度:O(1),只使用计数变量。

解法比较

方法时间 / 辅助空间优点限制 / 使用建议
枚举左右端点 + 累加O(n²) / O(1)适合小数据核对答案n=10⁵ 时不适合提交
前缀和 + 二分O(n log n) / O(n)直接练习区间变换和 lower_bound要求前缀有序;掌握题目进阶要求
滑动窗口O(n) / O(1)最优,代码简洁优先提交;必须解释正数保证的单调性

易错点与边界检查

  • 判定条件是 ≥,示例 [4,3] 的和恰好为 7 也必须接受。
  • 无解时返回 0,不能返回 n + 1 或负数。
  • 单元素刚好达标:target=4,nums=[4] → 1;单元素不足:target=4,nums=[3] → 0。
  • 整个数组才达标:target=6,nums=[1,2,3] → 3。
  • 负数会破坏窗口推理。例如 target=3,nums=[1,-1,3]:上述窗口会停留在长度 3,漏掉 [3]。该输入不属于 209 的约束,不能把算法无条件推广。
  • 二分查找右边界时,比较的是 P[left] + target,而非 target 本身。
面试追问:代码有两层循环,为什么仍然是 O(n)?

内层循环不在每次外层时重置 left。right 最多移动 n 次,left 也最多移动 n 次,窗口元素总共进入和离开各一次,因此总操作数为 O(n)。

第三阶段 · 前缀和与哈希计数

03 / PREFIX FREQUENCY

560 允许负数和零。区间和不再单调,于是将「寻找边界」转化为「查询历史状态出现了多少次」。

02LEETCODE 560 / MEDIUM和为 K 的子数组

LeetCode 原题 ↗

难度:中等 | 核心知识点:前缀和、哈希表、频次计数、先查询后插入

题意与约束

给定整数数组 nums 和整数 k,统计有多少个连续、非空子数组的元素和等于 k。不同下标区间分别计数,即使它们的元素值相同。

原题约束:1 ≤ n ≤ 2 × 10⁴;−1000 ≤ nums[i] ≤ 1000;−10⁷ ≤ k ≤ 10⁷。允许负数、零和负目标值。题意及约束见卡片顶部原题链接。

输入输出满足条件的下标区间
nums=[1,1,1],k=22[0,2) 与 [1,3)
nums=[1,2,3],k=32[0,2) 与 [2,3)

解法一 · 枚举左端点,向右累加(建立基线)

固定 left 后,把右端点逐步向右移动,每次只添加一个新元素。当累加和恰好为 k 时计数。不要因为当前和超过 k 就 break,后面可能有负数把总和拉回来。

Python 3 · 枚举并增量求和
from typing import List


class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        count = 0
        n = len(nums)
        for left in range(n):
            total = 0
            for right in range(left, n):
                total += nums[right]
                if total == k:
                    count += 1
        return count

关键代码:total 在每次开始新的 left 时清零,内层循环增量维护 [left,right] 的和。对 [1,1,1]、k=2,left=0 时在 right=1 命中一次,left=1 时在 right=2 命中一次,left=2 无命中,最终为 2。

正确性:每个非空区间都由唯一的一对 left、right 决定,两层循环恰好枚举一次,没有遗漏或重复。

时间复杂度:O(n²),共 n(n+1)/2 个区间。空间复杂度:O(1)。

这份代码适合验证思路和小规模对拍;原题上限下可能超时。若每次改成 sum(nums[left:right+1]),最坏会退化到 O(n³)。

解法二 · 前缀数组 + 枚举边界(公式桥梁)

先构建 P,再枚举所有满足 0 ≤ left < right ≤ n 的前缀边界,检查 P[right] − P[left] 是否为 k。这个版本没有减少区间数量,但清楚展示了从「子数组」到「前缀对」的映射,为下一步优化做准备。

Python 3 · 前缀数组枚举边界
from typing import List


class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        n = len(nums)
        prefix = [0] * (n + 1)
        for i, x in enumerate(nums):
            prefix[i + 1] = prefix[i] + x

        count = 0
        for right in range(1, n + 1):
            for left in range(right):
                if prefix[right] - prefix[left] == k:
                    count += 1
        return count

关键代码:range(right) 只枚举严格位于当前右边界之前的 left,排除空区间。[1,1,1] 的 P=[0,1,2,3],满足差为 2 的边界对为 (0,2)、(1,3),输出 2。

正确性:每个 [left,right) 与一对前缀边界一一对应,区间和公式直接给出判断条件。

时间复杂度:O(n²),单次区间求和 O(1) 仍需枚举。空间复杂度:O(n),保存前缀数组。

解法三 · 前缀和 + 频次哈希表(推荐)

固定当前右边界 right,把等式移项:

P[right] − P[left] = k ⇔ P[left] = P[right] − k

现在只需知道:在当前边界之前,值为 P[right]−k 的前缀出现了多少次。每出现一次,就对应一个不同的合法左端点。用字典 freq 记录频次,让查询从扫描历史前缀变成平均 O(1)。

  1. 初始 freq = {0: 1},登记一次空前缀。
  2. 读入 x,更新当前前缀 running。
  3. 将历史 running − k 的出现次数加入答案。
  4. 最后登记当前 running,供后面的右边界使用。
Python 3 · 前缀和 + 频次哈希表
from typing import List


class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        freq = {0: 1}  # 空前缀,代表从数组开头开始的区间
        running = 0
        count = 0

        for x in nums:
            running += x
            count += freq.get(running - k, 0)
            freq[running] = freq.get(running, 0) + 1

        return count
关键代码解释
  • freq.get(running - k, 0) 查的是所需历史前缀值,不是查询 k,也不是 running 本身。
  • 查到的是频次,所以直接累加这个数量;不能只加 1,也不能使用只表示「存在」的集合。
  • 必须先查历史再登记当前。否则 k=0 时会将当前前缀与自身配对,错误地计入空区间。
  • 只维护 running,无需保存整个 P 数组;字典仍可能有 O(n) 个不同的前缀值。
示例推演 A · nums=[1,1,1],k=2
读入下标 / 值running查 running−k本次增加登记后的 freq累计答案
开始0——{0:1}0
0 / 11−10{0:1, 1:1}0
1 / 1201{0:1, 1:1, 2:1}1
2 / 1311{0:1, 1:1, 2:1, 3:1}2
示例推演 B · nums=[1,-1,0],k=0

前缀边界的和值依次为 [0,1,0,0]。最后一个 0 到来之前,历史已经有两个和值为 0 的前缀,当前一步就应增加 2 个答案。

读入running查询时 freq[0]本次增加登记后 freq累计答案
111(本次查的是 1)0{0:1, 1:1}0
−1011 → [1,−1]{0:2, 1:1}1
0022 → [1,−1,0] 与 [0]{0:3, 1:1}3

逐步演示 · 先查询,再登记

固定 nums=[1,-1,0],k=0。每步展示查询前的字典与登记后的字典。

0 / 3
为什么一定正确?

处理 P[right] 时,字典恰好记录 P[0] 到 P[right−1] 的频次,这是循环不变量。每个被查到的 P[left] 都满足 left < right 和差为 k,因此对应一个合法非空子数组;反过来,每个合法子数组都会在其右边界被查询到。按右边界分组,区间既不重复也不遗漏。登记当前前缀后,不变量对下一轮继续成立。

时间复杂度:平均 O(n),哈希查询和更新平均 O(1)。空间复杂度:O(n),最多 n + 1 个不同前缀值。

上述时间分析使用常规哈希表平均复杂度模型;不是哈希冲突极端情况下的最坏界。

解法比较

解法时间辅助空间教学价值 / 提交建议
枚举 + 增量求和O(n²)O(1)建立正确性基线;大数据可能超时
前缀数组 + 边界枚举O(n²)O(n)推导前缀对;还没有消除二重枚举
前缀和 + 频次哈希表平均 O(n)O(n)推荐提交;用计数替代逐个匹配历史边界

易错点与边界检查

  • 遗漏空前缀:nums=[3]、k=3 应输出 1。没有 {0:1} 就会漏掉它。
  • 先插再查:nums=[0]、k=0 应输出 1。错误顺序会得到 2。
  • 把频次当布尔值:nums=[0,0,0]、k=0 应输出 6,即 3×4/2;三个右边界各贡献 1、2、3。
  • 遇到超出 k 就停止:nums=[3,-2]、k=1 应输出 1,扫描到 3 时不能提前结束。
  • 把子数组当子序列:nums=[1,2,1]、k=2 只有 [2] 合法,不能跳过中间的 2 拼出 [1,1]。
  • 负目标:nums=[-1,-1,1]、k=-1 → 3。Python int 可以容纳答案;换到固定宽度语言时应关注 n(n+1)/2 的上界。
面试追问:为什么不能直接用 209 的窗口?

因为负数使「右移增大、左移减小」不再成立,零还可能让多个不同左端点同时合法。560 统计全部区间,不是只找最短或最长。频次表保留了所有历史左边界的数量,不依赖数组符号。

第四阶段 · 从前缀和到前后缀乘积

04 / PREFIX × SUFFIX

前缀思想不只用于加法。238 将「排除当前位置」拆成互不相交的左右两部分,进一步练习状态含义与空间优化。

03LEETCODE 238 / MEDIUM除自身以外数组的乘积

LeetCode 原题 ↗

难度:中等 | 核心知识点:前缀乘积、后缀乘积、空积为 1、输出数组复用

题意与约束

返回数组 answer,其中 answer[i] 等于 nums 中除 nums[i] 以外所有元素的乘积。要求不使用除法,并在 O(n) 时间内完成;进阶要求 O(1) 额外空间,输出数组不计入额外空间。

原题约束:2 ≤ n ≤ 10⁵;−30 ≤ nums[i] ≤ 30,允许零与负数。原题保证相关前缀、后缀乘积及答案在 32 位整数范围内。力扣当前标题为「除了自身以外数组的乘积」,与题单中的写法对应同一题。

输入输出解释
nums=[1,2,3,4][24,12,8,6]例如 i=2:1×2×4=8
nums=[-1,1,0,-3,3][0,0,9,0,0]只有排除唯一零时,剩余乘积非零

为什么不能套「总积 ÷ 当前元素」?题目禁止除法;nums[i]=0 时也无法这样计算。前后缀分解自然处理零和负数,不需要特判零的数量。

解法一 · 显式保存左右乘积

定义 left[i] 为 i 左侧所有元素的乘积,right[i] 为 i 右侧所有元素的乘积,二者都不包含 nums[i]。空集合的乘积为 1,所以 left[0]=1、right[n−1]=1。

answer[i] = left[i] × right[i]
排除下标2:左侧1乘2,右侧4,合并得到8123(排除)4left[2] = 1 × 2right[2] = 4answer[2] = 2 × 4 = 8
左右两段刚好覆盖除当前位置外的所有元素,既无遗漏,也无重叠。
Python 3 · 显式前后缀数组
from typing import List


class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        left = [1] * n
        right = [1] * n

        for i in range(1, n):
            left[i] = left[i - 1] * nums[i - 1]
        for i in range(n - 2, -1, -1):
            right[i] = right[i + 1] * nums[i + 1]

        return [left[i] * right[i] for i in range(n)]
关键代码与示例推演

nums[i - 1] 和 nums[i + 1] 是「排除自己」的关键。计算 left 从左向右,计算 right 从右向左,保证依赖的状态已经就绪。对 nums=[1,2,3,4]:

inums[i]left[i]:不含 iright[i]:不含 ianswer[i]
0112×3×4 = 2424
1213×4 = 1212
231×2 = 248
341×2×3 = 616

正确性:递推式分别构造左右两侧的乘积;两段不相交且并集恰好是除 i 以外的所有位置,将它们相乘就是定义要求的答案。

时间复杂度:O(n),三次线性遍历。空间复杂度:O(n) 额外空间;含输出仍为 O(n)。

解法二 · 输出数组存左积,单变量维护右积(推荐)

最终答案还未生成时,answer[i] 可以先存 i 左侧乘积。再从右向左扫描,用 suffix 保存 i 右侧乘积,直接乘进 answer[i],就不需要 right 数组。

正向循环开始处理 i 时,prefix 恰好是不含 i 的左积;反向循环开始处理 i 时,suffix 恰好是不含 i 的右积。两轮都必须先使用旧状态,再把 nums[i] 纳入累计。

Python 3 · 复用输出数组 + 右积变量
from typing import List


class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        answer = [1] * n

        prefix = 1
        for i in range(n):
            answer[i] = prefix
            prefix *= nums[i]

        suffix = 1
        for i in range(n - 1, -1, -1):
            answer[i] *= suffix
            suffix *= nums[i]

        return answer
关键代码解释
  • answer[i] = prefix 放在 prefix *= nums[i] 之前,确保左积不包含当前元素。
  • range(n - 1, -1, -1) 的结束边界 −1 不包含在遍历中,所以最后会处理 i=0。
  • answer[i] *= suffix 使用当前右积后,再纳入 nums[i];顺序反过来会把自身错误乘入。
  • 代码没有修改输入数组,答案是一个新列表。
示例推演 · 两次扫描

正向扫描时,prefix 依次以 1、1、2、6 写入 answer,得到 [1,1,2,6],然后反向合并:

i使用前 suffixanswer[i] × suffix更新 suffix 后此时 answer
316×1 = 64[1,1,2,6]
242×4 = 812[1,1,8,6]
1121×12 = 1224[1,12,8,6]
0241×24 = 2424[24,12,8,6]

零元素推演:nums=[-1,1,0,-3,3],正向得到 [1,-1,-1,0,0]。反向访问 i=4、3、2、1、0 时,使用前的 suffix 分别为 1、3、−9、0、0;相乘后得到 [0,0,9,0,0]。零在扫描中自然传播。

正确性:第一轮归纳保证 answer[i] 为左侧乘积;第二轮归纳保证使用前 suffix 为右侧乘积。因此更新后的每项与解法一完全相同,压缩的是存储方式,状态定义未变。

时间复杂度:O(n),两次线性扫描。空间复杂度:O(1) 额外空间;计入输出数组为 O(n)。

复杂度采用题目规定范围下的整数运算模型。输出数组有 n 项,不能把「O(1) 额外空间」说成总空间 O(1)。

解法比较

方法时间 / 额外空间优点使用建议
显式 left + rightO(n) / O(n)状态直观,方便画表推演先理解排除当前元素的定义
复用 answer + suffixO(n) / O(1)达到原题进阶要求,零无需分支推荐提交,说明输出不计额外空间

易错点与边界检查

  • 累计初值是 1,不是 0;乘法的单位元与加法不同。
  • 不要用前缀总积相除来求区间乘积,零会导致信息丢失,且本题禁止除法。
  • 没有零:[2,3] → [3,2],同时检查首尾的空积。
  • 只有一个零:[0,2,3] → [6,0,0];两个零:[0,0,3] → [0,0,0]。
  • 负数:[−1,2,−3] → [−6,3,−2],按正常乘法处理符号。
  • 切片和逐项重算乘积会造成 O(n²),不满足线性要求。
面试追问:前缀乘积与前缀和有什么不同?

二者都复用已算过的聚合结果,但加法可以通过减法消去左段;乘法遇到零时不能可靠地通过除法恢复区间。本题改为组合左右两段,依赖的是乘法结合律与空积为 1。

复盘 · 形成选型能力

看到题目,先问这三个问题

判断问题这组题中的结论选择
需要查询某段和,还是统计全部区间?区间和 → 两个前缀相减;计数 → 查询历史频次P[r]−P[l] / freq[running−k]
元素符号是否保证单调?209 全为正数;560 允许负数、零209 二分或窗口;560 频次表
当前位置要排除,左右是否可以独立聚合?238 左边与右边的乘积独立前缀乘积 × 后缀乘积

核心模板速查

记忆锚点定义或顺序检查点
n + 1 个边界P[0]=0;P[i+1]=P[i]+nums[i]P[i] 不含 nums[i]
移项后查历史P[left] = P[right] − k查频次,不是只查存在
空前缀登记一次freq={0:1}允许从下标 0 起步
560:先查再登记running += x → count += freq[running−k] → freq[running] += 1避免空子数组
209:先记再收缩while total ≥ target:更新长度 → 移出 nums[left] → left += 1≥;while;长度 +1
238:先用再累计answer[i]=prefix → prefix*=nums[i];反向同理空积 1;输出与辅助空间分开

面试前的最小回归用例集

题号输入期望检测的错误
209target=4,nums=[4]1边界相等与长度 1
209target=7,nums=[2,3,1,2,4,3]2多次收缩
209target=100,nums=[1,2,3]0无解哨兵
560nums=[3],k=31空前缀
560nums=[0,0,0],k=06先查后插、重复前缀
560nums=[1,-1,0],k=03负数、频次累加
238nums=[2,3][3,2]首尾空积
238nums=[0,2,3][6,0,0]唯一零
238nums=[0,0,3][0,0,0]两个零
238nums=[-1,2,-3][-6,3,-2]符号与排除自身

掌握程度自测

题目是否掌握由题卡按钮标记;下面的能力清单独立保存,不影响 3 题总进度。

阶段性学习计划 · 4 天 + 间隔复习

安排任务验收标准
Day 1 · 30–45 分钟阶段 1;在纸上写出 P 并操作区间演示任给合法 l、r,30 秒内算对区间和
Day 2 · 45–60 分钟阶段 2;分别默写 209 的二分与窗口说清 while 的必要性和总时间 O(n)
Day 3 · 45–60 分钟阶段 3;从 O(n²) 推到 560 哈希解手推 [0,0,0] 得 6;解释空前缀与顺序
Day 4 · 40–60 分钟阶段 4;写出 238 两种方法,再闭卷复做三题正确处理零和负数;分清总空间与额外空间
完成后第 3 / 7 / 14 天每次随机选一题限时 15 分钟,不先看代码答出状态定义、正确性、复杂度和至少 3 个边界用例
闭卷练习:把三题各压缩成一句话

209:正数保证单调,窗口达标就记长度并持续收缩。
560:当前前缀减 k 得到所需历史前缀值,查频次后再登记自己。
238:每个位置的答案等于不含自己的左积乘右积,输出先存左积再反向合并。

说明:原题链接需要联网;手册正文、语法高亮、图解、复制、主题、学习进度和演示均在本文件内。进度属于当前浏览器与文件位置;更换浏览器、移动文件或清理浏览器数据后,可能需要重新标记。打印时会展开自测答案。