1. 题目背景与核心挑战
这道来自力扣hot100的经典题目"和为 K 的子数组"(LeetCode 560. Subarray Sum Equals K)看似简单,实则暗藏玄机。作为数组类问题的典型代表,它考察的不仅是基础编程能力,更是对算法优化思维的深度理解。
题目要求很简单:给定一个整数数组nums和一个整数k,统计该数组中和为k的连续子数组的个数。比如nums = [1,1,1],k=2时,返回2(因为前两位和后两位的和都是2)。但当你真正开始coding时,会发现暴力解法在数据量稍大时就会超时。
关键提示:这道题O(n²)的暴力解法虽然直观,但在力扣上会直接超时。必须找到O(n)或O(nlogn)的优化方案才能通过所有测试用例。
2. 暴力解法与优化思路
2.1 直观的暴力枚举法
最直接的思路是双重循环枚举所有可能的子数组:
def subarraySum(nums, k): count = 0 n = len(nums) for i in range(n): sum = 0 for j in range(i, n): sum += nums[j] if sum == k: count += 1 return count这种方法时间复杂度O(n²),空间复杂度O(1)。当n=10⁵时,计算量将达到10¹⁰次操作,远超力扣的时间限制。
2.2 前缀和优化思路
聪明的解法是利用前缀和(Prefix Sum)技巧。前缀和的定义是:prefix[i]表示nums[0]到nums[i-1]的和。这样任意子数组nums[i..j]的和可以表示为prefix[j+1] - prefix[i]。
基于这个性质,我们可以将问题转化为:找到所有满足prefix[j+1] - prefix[i] == k的(i,j)对。这等价于寻找prefix[i] == prefix[j+1] - k。
3. 哈希表优化实现
3.1 算法核心思想
使用哈希表存储前缀和的出现次数,可以在O(1)时间内查询所需的前缀和值。具体步骤:
- 初始化哈希表prefix_sum = {0:1}(和为0出现1次)
- 维护当前前缀和current_sum
- 遍历数组时,计算current_sum += nums[i]
- 检查current_sum - k是否在哈希表中,如果在则count += prefix_sum[current_sum - k]
- 更新current_sum的出现次数
3.2 完整实现代码
def subarraySum(nums, k): count = 0 prefix_sum = {0: 1} current_sum = 0 for num in nums: current_sum += num if (current_sum - k) in prefix_sum: count += prefix_sum[current_sum - k] prefix_sum[current_sum] = prefix_sum.get(current_sum, 0) + 1 return count3.3 复杂度分析
- 时间复杂度:O(n),只需一次遍历
- 空间复杂度:O(n),最坏情况下需要存储n个不同的前缀和
4. 边界条件与特殊测试用例
4.1 必须考虑的边界情况
- 空数组:nums = [], k=0 → 返回0
- 单个元素等于k:nums = [1], k=1 → 返回1
- 多个相同元素:nums = [1,1,1], k=2 → 返回2
- 包含负数:nums = [-1,-1,1], k=0 → 返回1
- k=0的特殊情况:nums = [0,0,0], k=0 → 返回6
4.2 测试用例验证表
| 测试用例 | 预期输出 | 说明 |
|---|---|---|
| [1], 1 | 1 | 单个元素 |
| [1,2,3], 3 | 2 | 正常情况 |
| [1,-1,0], 0 | 3 | 包含0和负数 |
| [0,0,0], 0 | 6 | 全0数组 |
| [1], 0 | 0 | k=0无解 |
5. 算法优化技巧与心得
5.1 关键优化点
- 哈希表初始化必须包含{0:1},否则会漏算从数组开头开始的子数组
- 更新哈希表应该在检查之后,避免重复计数
- 使用get方法可以简化代码,避免KeyError异常
5.2 常见错误排查
错误:忘记初始化{0:1}
- 现象:漏算从第一个元素开始的子数组
- 示例:nums = [1], k=1会返回0而不是1
错误:先更新哈希表再检查
- 现象:会错误计算空子数组
- 示例:nums = [1], k=0会返回1而不是0
错误:使用数组存储前缀和
- 现象:查找效率低导致超时
- 解决方案:必须使用哈希表实现O(1)查找
5.3 性能优化实测
在力扣平台上实测结果:
- 暴力解法:在n=20000时超时(>2000ms)
- 前缀和+哈希表:相同规模仅需40ms左右
6. 同类问题扩展
掌握了这个技巧后,可以解决一系列类似问题:
- 最长的和为k的子数组(LeetCode 325)
- 和为k的子矩阵(LeetCode 1074)
- 乘积为k的子数组(LeetCode 713)
- 0和1数量相等的子数组(LeetCode 525)
这类问题的共同特点是:
- 都需要统计满足某种条件的连续子数组/子矩阵
- 都可以通过前缀和/积+哈希表的组合来优化
- 都需要特别注意边界条件和初始化值
7. 刷题策略建议
对于力扣hot100这类高频题库:
- 先理解暴力解法,确保完全掌握问题本质
- 分析暴力解法的时间瓶颈在哪里
- 寻找可以将O(n²)降为O(n)或O(nlogn)的数学性质或数据结构
- 特别注意边界条件和特殊测试用例
- 记录每种题型的解题模板,形成肌肉记忆
这道"和为k的子数组"作为hot100的经典题目,几乎包含了数组类问题的所有考察要点。掌握它的优化思路后,你会发现很多其他题目都是这个模式的变种。这也是为什么它会被选入hot100题库 - 它不仅考察编码能力,更考察你能否从暴力解法中发现优化规律的能力。