GitHub_Trending/le/LeetCode-Book:前缀和与差分算法实战
1. 算法痛点与核心价值
你是否在处理数组区间问题时频繁遭遇以下困境?遍历嵌套导致超时(O(n²))、边界条件复杂难以调试、多次查询场景下重复计算?本文将系统讲解前缀和(Prefix Sum)与差分(Difference Array)两种预处理技术,带你实现从暴力解法到O(n)甚至O(1)的效率跃迁。
读完本文你将掌握
- 前缀和数组构建与三类核心应用(区间和查询/子数组和问题/二维矩阵优化)
- 差分算法的数学原理与实战技巧(区间增减/航班预订问题)
- 基于LeetCode-Book项目源码的多语言实现(Python/Java/C++)
- 10+企业级真题优化案例(字节跳动/阿里/腾讯高频面试题)
2. 前缀和算法:从重复计算到一次预处理
2.1 核心定义与数学本质
前缀和数组prefix是原数组nums的衍生数组,满足prefix[i] = nums[0] + nums[1] + ... + nums[i-1]。其本质是通过空间换时间,将任意区间和sum(nums[i..j])转化为prefix[j+1] - prefix[i]的O(1)计算。
2.2 基础实现模板(Python/Java/C++)
def build_prefix(nums):
n = len(nums)
prefix = [0] * (n + 1)
for i in range(n):
prefix[i+1] = prefix[i] + nums[i]
return prefix
# 区间和查询
def range_sum(prefix, i, j):
return prefix[j+1] - prefix[i]
public int[] buildPrefix(int[] nums) {
int n = nums.length;
int[] prefix = new int[n + 1];
for (int i = 0; i < n; i++) {
prefix[i+1] = prefix[i] + nums[i];
}
return prefix;
}
public int rangeSum(int[] prefix, int i, int j) {
return prefix[j+1] - prefix[i];
}
vector<int> buildPrefix(vector<int>& nums) {
int n = nums.size();
vector<int> prefix(n + 1, 0);
for (int i = 0; i < n; i++) {
prefix[i+1] = prefix[i] + nums[i];
}
return prefix;
}
int rangeSum(vector<int>& prefix, int i, int j) {
return prefix[j+1] - prefix[i];
}
2.3 实战案例1:最大子数组和的前缀和解法
虽然LeetCode-Book中selected_coding_interview/docs/53. 最大子数组和.md采用动态规划解法,但前缀和结合哈希表可实现同等效率:
def max_sub_array(nums):
prefix = 0
min_prefix = 0
max_sum = -float('inf')
for num in nums:
prefix += num
max_sum = max(max_sum, prefix - min_prefix)
min_prefix = min(min_prefix, prefix)
return max_sum
原理对比表
| 解法 | 时间复杂度 | 空间复杂度 | 核心思想 |
|---|---|---|---|
| 动态规划 | O(n) | O(1) | dp[i] = max(nums[i], dp[i-1]+nums[i]) |
| 前缀和+哈希 | O(n) | O(1) | max(prefix[j]-min_prefix[i]) (i<j) |
2.4 实战案例2:和为s的连续正数序列
剑指Offer第57题II(sfo_57ii_consecutive_numbers_with_sum_s_s2.py)采用双指针法,其本质可视为前缀和的滑动窗口优化:
def find_continuous_sequence(target):
i, j, s, res = 1, 2, 3, []
while i < j:
if s == target:
res.append(list(range(i, j + 1)))
if s >= target:
s -= i
i += 1
else:
j += 1
s += j
return res
前缀和视角解读:当窗口[i,j]的和s = prefix[j] - prefix[i-1],通过调整i/j实现O(n)复杂度,避免传统前缀和的O(n²)遍历。
3. 差分算法:区间增减的高效处理
3.1 算法定义与原理解析
差分数组diff满足diff[i] = nums[i] - nums[i-1](diff[0] = nums[0])。对区间[l,r]增减val时,仅需修改diff[l] += val和diff[r+1] -= val,最后通过前缀和恢复原数组。
3.2 基础实现模板
def build_diff(nums):
n = len(nums)
diff = [0] * n
diff[0] = nums[0]
for i in range(1, n):
diff[i] = nums[i] - nums[i-1]
return diff
def range_add(diff, l, r, val):
diff[l] += val
if r + 1 < len(diff):
diff[r+1] -= val
def restore_array(diff):
n = len(diff)
nums = [0] * n
nums[0] = diff[0]
for i in range(1, n):
nums[i] = nums[i-1] + diff[i]
return nums
3.3 实战案例:航班预订统计
LeetCode 1109题的差分解法(项目中未直接实现,基于模板扩展):
public int[] corpFlightBookings(int[][] bookings, int n) {
int[] diff = new int[n + 2]; // 避免r+1越界
for (int[] book : bookings) {
int l = book[0], r = book[1], val = book[2];
diff[l] += val;
diff[r+1] -= val;
}
// 恢复原数组
int[] res = new int[n];
res[0] = diff[1];
for (int i = 1; i < n; i++) {
res[i] = res[i-1] + diff[i+1];
}
return res;
}
4. 高级应用与项目源码关联
4.1 二维前缀和:矩阵区域和
虽然项目中未直接提供,但可基于LCR 130. 衣橱整理.md的矩阵遍历思想扩展:
vector<vector<int>> build2DPrefix(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> prefix(m+1, vector<int>(n+1, 0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1];
}
}
return prefix;
}
4.2 前缀和+位运算:寻找单身数字
剑指Offer第56题I(sfo_56i_single_number_i_s1.py)可结合前缀异或和优化:
def singleNumbers(nums):
xor_sum = 0
for num in nums:
xor_sum ^= num
# 后续分组逻辑省略...
5. 学习路线与项目实践指南
5.1 必刷题目清单(基于项目目录)
- 前缀和入门:
selected_coding_interview/docs/53. 最大子数组和.md - 差分应用:
sword_for_offer/docs/剑指 Offer 57 - II. 和为 s 的连续正数序列.md - 二维扩展:
leetbook_ioa/docs/LCR 130. 衣橱整理.md - 综合实战:
selected_coding_interview/docs/304. 二维区域和检索 - 矩阵不可变.md(需自行实现)
5.2 性能优化 checklist
- ✅ 前缀和数组大小设为n+1,避免边界判断
- ✅ 差分修改时注意r+1是否越界
- ✅ 多次查询场景优先考虑前缀和预处理
- ✅ 区间修改密集场景必用差分算法
5.3 企业面试高频考点
- 前缀和与哈希表结合的子数组和问题(字节跳动)
- 差分算法的航班预订/座位预约实现(阿里)
- 二维前缀和的图像模糊处理(腾讯)
6. 总结与展望
前缀和与差分作为两种预处理技术,在数组区间问题中展现出"一次预处理,多次高效查询"的强大能力。通过LeetCode-Book项目中的实战案例分析可见,这两种算法虽未被单独成册讲解,但其思想已渗透在动态规划、双指针等多种解题方法中。
建议结合项目中selected_coding_interview/docs/3. 无重复字符的最长子串.md等滑动窗口题目,进一步体会前缀和思想的变体应用。未来刷题时,可尝试用前缀和重解已AC的子数组问题,培养算法优化思维。
收藏本文,关注项目更新,下期将带来《贪心算法在区间调度中的极致优化》。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



