news 2026/8/25 12:43:36

前缀和与差分数组——从O(n²)到O(1)的降维打击

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前缀和与差分数组——从O(n²)到O(1)的降维打击

前缀和是面试中"最被低估"的优化技巧。预计算 O(n) + 查询 O(1) 的经典模式,掌握后能解决几乎所有"区间和"类问题。

一、引言

🤔思考:面试官给你一个数组,要你快速回答"索引 2 到 7 的和是多少"。你写了一个循环累加,O(n) 搞定。面试官说:"我有 100 万次这样的查询,怎么办?" 你的 O(n) × 100 万 = 1000 亿次操作,显然不行。

这就是前缀和(Prefix Sum)要解决的问题。

前缀和是一种预计算优化技巧:先花 O(n) 时间预处理一个前缀和数组,然后每次区间和查询只需要 O(1) 时间。这是典型的空间换时间——用 O(n) 的额外空间,把查询从 O(n) 降到 O(1)。

本文围绕两个经典题目展开:

  1. 303. 区域和检索 - 数组不可变(Easy)—— 前缀和的入门模板,展示静态前缀和的核心用法

  2. 560. 和为K的子数组(Medium)—— 前缀和 + 哈希表优化,从静态预计算到动态统计的进阶

本期是「数据结构与算法面试精讲」系列第16篇。

二、前缀和基础

2.1 什么是前缀和?

前缀和(Prefix Sum)是指数组从开头到某个位置的所有元素之和。

对于数组nums,定义前缀和数组prefix,其中prefix[i]表示nums[0]nums[i-1]的和(即不包含nums[i]本身):

prefix[i] = sum(nums[0..i-1])

这种定义方式(左闭右开)的好处是:prefix[0] = 0,边界处理更统一。

2.2 前缀和的计算与使用

计算前缀和数组:

prefix[0] = 0 for i in 1..n: prefix[i] = prefix[i-1] + nums[i-1]

使用前缀和查询区间和:

sumRange(left, right) = prefix[right+1] - prefix[left]

查询示例:sumRange(1, 3)=nums[1] + nums[2] + nums[3]= 2 + 3 + 4 = 9 通过前缀和:prefix[3+1] - prefix[1]=prefix[4] - prefix[1]= 10 - 1 = 9 ✅

2.3 暴力 vs 前缀和对比

算法

预处理时间

单次查询时间

空间

暴力枚举

O(1)

O(n)

O(1)

前缀和

O(n)

O(1)

O(n)

关键洞察:当查询次数远多于数组长度时,前缀和的效果最显著。一次预计算 O(n) 的成本,被后续大量 O(1) 查询摊薄。

三、303. 区域和检索 - 数组不可变(Easy)

3.1 题目描述

给定一个整数数组nums,处理多个查询sumRange(i, j),返回数组从索引ij的元素和。

示例:

输入: nums = [-2, 0, 3, -5, 2, -1] sumRange(0, 2) → 1 (-2 + 0 + 3 = 1) sumRange(2, 5) → -1 (3 + (-5) + 2 + (-1) = -1) sumRange(0, 5) → -3 (全部元素之和 = -3)

3.2 前缀和解法

核心思路:在构造函数中预计算前缀和数组,sumRange直接查表。

class NumArray: def __init__(self, nums: List[int]): self.prefix = [0] * (len(nums) + 1) for i in range(len(nums)): self.prefix[i + 1] = self.prefix[i] + nums[i] def sumRange(self, left: int, right: int) -> int: return self.prefix[right + 1] - self.prefix[left]
class NumArray { private int[] prefix; public NumArray(int[] nums) { prefix = new int[nums.length + 1]; for (int i = 0; i < nums.length; i++) { prefix[i + 1] = prefix[i] + nums[i]; } } public int sumRange(int left, int right) { return prefix[right + 1] - prefix[left]; } }
class NumArray { private: vector<int> prefix; public: NumArray(vector<int>& nums) { prefix.resize(nums.size() + 1, 0); for (int i = 0; i < nums.size(); i++) { prefix[i + 1] = prefix[i] + nums[i]; } } int sumRange(int left, int right) { return prefix[right + 1] - prefix[left]; } };

复杂度分析:

  • 时间复杂度:初始化 O(n),查询 O(1)

  • 空间复杂度:O(n)

3.3 面试追问

追问1:如果数组很大(比如 10^9 个元素),无法全量存内存怎么办?

分段前缀和——把数组分成若干块(block),每块预计算块内和。查询时完整的块直接用块内和,不完整的块逐个累加。块大小设为sqrt(n),查询复杂度 O(√n),空间 O(√n)。

追问2:如果数组在查询之间会改变呢?

那就不能用静态前缀和。需要树状数组(Fenwick Tree)或线段树(Segment Tree),支持动态更新和区间查询。

四、560. 和为K的子数组(Medium)

4.1 题目描述

给定一个整数数组nums和一个整数k,统计该数组中和为k连续子数组的个数。

示例 1:

输入: nums = [1, 1, 1], k = 2 输出: 2 解释: [1, 1](索引0-1)和 [1, 1](索引1-2)两个子数组

示例 2:

输入: nums = [1, 2, 3], k = 3 输出: 2 解释: [1, 2](索引0-1)和 [3](索引2)

4.2 暴力解法(O(n²))

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

nums.length达到 2×10⁴ 时,O(n²) 的 4 亿次操作必然超时。

4.3 前缀和 + 哈希表优化(O(n))

核心洞察:子数组[i+1..j]的和 =prefix[j] - prefix[i]。要统计prefix[j] - prefix[i] = k的个数,等价于遍历到j时,统计之前出现过多少个prefix[j] - k

公式推导:

子数组 [i+1..j] 的和 = k → prefix[j] - prefix[i] = k → prefix[i] = prefix[j] - k

三语言实现:

class Solution: def subarraySum(self, nums: List[int], k: int) -> int: prefix_map = {0: 1} prefix_sum = 0 count = 0 for num in nums: prefix_sum += num count += prefix_map.get(prefix_sum - k, 0) prefix_map[prefix_sum] = prefix_map.get(prefix_sum, 0) + 1 return count
class Solution { public int subarraySum(int[] nums, int k) { Map<Integer, Integer> prefixMap = new HashMap<>(); prefixMap.put(0, 1); int prefixSum = 0, count = 0; for (int num : nums) { prefixSum += num; count += prefixMap.getOrDefault(prefixSum - k, 0); prefixMap.put(prefixSum, prefixMap.getOrDefault(prefixSum, 0) + 1); } return count; } }
class Solution { public: int subarraySum(vector<int>& nums, int k) { unordered_map<int, int> prefixMap; prefixMap[0] = 1; int prefixSum = 0, count = 0; for (int num : nums) { prefixSum += num; count += prefixMap[prefixSum - k]; prefixMap[prefixSum]++; } return count; } };

复杂度分析:时间复杂度 O(n),空间复杂度 O(n)。

4.4 面试追问

追问1:如果数组包含负数怎么办?解法不变。560 题本身就支持负数。追问2:如果要求返回子数组的起始和结束位置?哈希表改存prefix_sum → [index_list]

五、差分数组简介

差分数组(Difference Array)与前缀和是"对偶"关系。前缀和解决"多次区间查询",差分数组解决"多次区间修改"。

定义:diff[i] = nums[i] - nums[i-1](其中diff[0] = nums[0]

核心操作:

  • 区间[l, r]统一加valdiff[l] += val, diff[r+1] -= val

  • 恢复数组:nums[i] = nums[i-1] + diff[i]

特性

前缀和

差分数组

解决的问题

多次区间查询

多次区间修改

核心操作

预计算 → O(1) 查询

区间修改 O(1) → 恢复

典型应用

303, 560, 304

1109, 1094

六、家族题梯度

题号

题目

难度

核心技巧

303

区域和检索

⭐ Easy

静态前缀和模板

304

二维区域和检索

⭐⭐ Medium

二维前缀和

560

和为K的子数组

⭐⭐ Medium

前缀和 + 哈希表

523

连续子数组和

⭐⭐ Medium

前缀和 + 哈希表(模)

974

和可被K整除的子数组

⭐⭐ Medium

前缀和 + 模运算

1109

航班预订统计

⭐⭐ Medium

差分数组模板

1094

拼车

⭐⭐ Medium

差分数组应用

学习建议:先做 303 和 304 掌握静态前缀和模板 → 再做 560 和 523 理解前缀和 + 哈希表 → 最后做 1109 和 1094 掌握差分数组。

参考资料

  • LeetCode 303. 区域和检索 - 数组不可变

  • LeetCode 560. 和为K的子数组

  • LeetCode 1109. 航班预订统计(差分数组典型题)

标签:前缀和, 差分数组, 算法面试, LeetCode, 数据结构

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 12:41:13

快餐出海,不是把店开出去,而是把供应链搬出去!10月杭州中餐出海研讨会,快餐/简餐出海的供应链适配与本土化策略。限席!

钱塘文旅商学院以高校产教融合为载体、以文化出海为核心赛道、面向全球的文旅商学平台。以文化为根、以品牌为体、以全球化为目标&#xff0c;打造 “校园直连全球舞台” 的轻资产出海样板&#xff0c;助力中国文化与中国品牌成为世界主流。主办单位&#xff1a;钱塘文旅商学院…

作者头像 李华
网站建设 2026/8/25 12:38:40

构建AI编程智能体:以DeepSeek Hermes为大脑的多工具协同工作流

如果你最近在关注 AI 编程工具&#xff0c;可能会发现一个有趣的现象&#xff1a;一边是 Claude Code、Cursor 这类“智能编辑器”在努力理解你的意图并生成代码&#xff0c;另一边是 DeepSeek Coder、Codex 这类“代码生成模型”在提供强大的补全能力。但你是否想过&#xff0…

作者头像 李华
网站建设 2026/8/25 12:37:27

AI大模型与数学·第49课 级数刷题集训6:泰勒级数完整实战+余项误差分析——大模型轻量化、截断近似底层数学

本课定位 上一课第48课我们学完幂级数完整体系&#xff0c;明确核心结论&#xff1a;泰勒级数是定点展开的幂级数。 本节课完成泰勒全流程闭环&#xff1a;泰勒展开完整计算、麦克劳林特例、三大余项公式、误差定量估算&#xff0c;同时完整对接AI工业场景&#xff1a;模型截断…

作者头像 李华
网站建设 2026/8/25 12:31:21

Windows局域网联机全攻略:从文件共享到游戏联机与Docker部署

你还在为和朋友联机打游戏、共享文件、或者搭建一个内部测试环境而头疼吗&#xff1f;明明大家就在同一个屋檐下&#xff0c;网络却像隔着一座山。无论是经典的《红色警戒2》、《魔兽争霸3》&#xff0c;还是热门的《饥荒联机版》、《恐鬼症》&#xff0c;亦或是想用OBS做个局域…

作者头像 李华
网站建设 2026/8/25 12:22:26

迷你PC构建Proxmox集群:万兆网络规划与配置实战

你花了几千块买了几台迷你PC&#xff0c;打算组个Proxmox集群&#xff0c;想着能跑点虚拟机、容器&#xff0c;还能玩玩高可用。硬件都到了&#xff0c;系统也装好了&#xff0c;但当你准备把它们用万兆网卡连起来&#xff0c;让数据在节点间高速流动时&#xff0c;却发现事情没…

作者头像 李华
网站建设 2026/8/25 12:17:31

Qwen3.8本地推理性能优化实战:vLLM、量化与参数调优指南

这次我们来看一个针对 Qwen3.8 模型推理性能的优化项目。Qwen3.8 作为通义千问团队开源的最新系列模型&#xff0c;在代码、数学和推理能力上表现突出&#xff0c;但直接部署时&#xff0c;其推理速度&#xff0c;尤其是长序列或复杂思考&#xff08;“雷霆大思考”&#xff09…

作者头像 李华