想系统提升编程能力、查看更完整的学习路线,欢迎访问 AI Compass:github.com/tingaicompa… 仓库持续更新刷题题解、Python 基础和 AI 实战内容,适合想高效进阶的你。
📖 第4课:和为K的子数组
模块:哈希表 | 难度:Medium ⭐⭐⭐ LeetCode 链接:leetcode.cn/problems/su… 前置知识:第1课(两数之和)、第3课(最长连续序列) 预计学习时间:30分钟
🎯 题目描述
给你一个整数数组和一个整数 k,请你统计并返回该数组中和为 k 的连续子数组的个数。
注意:子数组是数组中连续的非空序列。
示例:
输入:nums = [1, 1, 1], k = 2
输出:2
解释:有2个和为2的子数组:[1,1] 和 [1,1](下标不同)
输入:nums = [1, 2, 3], k = 3
输出:2
解释:有2个和为3的子数组:[1,2] 和 [3]
约束条件:
1 <= nums.length <= 2 * 10^4(数组最多2万个元素)-1000 <= nums[i] <= 1000(元素可以为负数、零或正数)-10^7 <= k <= 10^7
🧪 边界用例(面试必考)
| 用例类型 | 输入 | 期望输出 | 考察点 |
|---|---|---|---|
| 单元素匹配 | nums=[3], k=3 | 1 | 单个元素本身就是答案 |
| 单元素不匹配 | nums=[1], k=2 | 0 | 无符合条件的子数组 |
| 含负数 | nums=[1,-1,0], k=0 | 3 | 负数使得多种组合可能:[-1,1]、[0]、[1,-1,0] |
| 全为零 | nums=[0,0,0], k=0 | 6 | n个0有 n*(n+1)/2 个子数组,全为0 |
| 多个解 | nums=[1,1,1], k=2 | 2 | [1,1]有两个(下标不同) |
| 无解 | nums=[1,2,3], k=10 | 0 | 所有子数组和都小于k |
| 大规模 | n=2*10^4 | — | 暴力法 O(n²) 约4亿次操作,会超时 |
💡 思路引导
生活化比喻
想象你在记录每天的收支流水账,现在要找"哪些时间段的总收支正好是 1000 元"。
🐌 笨办法:枚举所有可能的时间段(从第 i 天到第 j 天),每段都从头到尾加一遍算总和,看是否等于 1000。这样做的话,有多少个时间段?大约 n²/2 个!每段还要加 O(n) 次,总共 O(n³) ——太慢了!
🤔 稍聪明一点:用"前缀和"优化计算。先算出"从第 1 天到第 i 天的累计总和",这样任意时间段 [i, j] 的和就是
前缀和[j] - 前缀和[i-1],查询变成 O(1)。但还是要枚举 n²/2 个时间段,总体 O(n²)。🚀 最聪明办法:一边算前缀和,一边用"哈希表记住之前见过的前缀和"。当你算到第 j 天的前缀和时,只需要在哈希表里查:"之前有没有出现过
当前前缀和 - 1000?"如果有,说明存在某个时间段 [i+1, j] 的和正好是 1000!而且,如果这个前缀和出现过 3 次,说明有 3 个不同的起点都能到达当前位置且和为 k。这样只需要遍历一次,O(n) 搞定!关键魔法:"我要找和为 k 的区间"转化为"我要找之前出现过的某个特定前缀和"——这就是哈希表的用武之地!
关键洞察
子数组和问题的本质:区间和 = 两个前缀和的差。用哈希表记录"每个前缀和出现的次数",遍历时查找 当前前缀和 - k 是否出现过,出现几次就有几个符合条件的子数组。
🧠 解题思维链
这一节模拟你在面试中"从零开始思考"的过程。
Step 1:理解题目 → 锁定输入输出
- 输入:一个整数数组
nums(长度 1~2万),一个整数k - 输出:一个整数,表示和为 k 的连续子数组的个数(不是子数组本身!)
- 限制:元素可以为负数/零/正数,k 也可以是任意整数
Step 2:先想笨办法(暴力枚举)
最直接的想法——枚举所有连续子数组,计算每个的和。
- 外层循环:起点 i
- 内层循环:终点 j (j >= i)
- 每次计算
sum(nums[i:j+1])是否等于 k - 时间复杂度:O(n²) ~ O(n³)(取决于求和方式)
- 瓶颈在哪:重复计算。比如
[1,2,3],算[1,2]的和是 3,算[1,2,3]时又从头加一遍,浪费!
Step 3:瓶颈分析 → 优化方向
暴力法的问题:每次求子数组和都要重新累加。能不能 O(1) 求出任意区间和?
- 核心问题:"区间 [i, j] 的和"需要重复计算
- 优化思路:用前缀和(prefix sum)预处理,
区间和 = preSum[j] - preSum[i-1],查询变 O(1) - 但还是要枚举 n²/2 个区间,能不能更快?
Step 4:数学转换 + 哈希表
关键变换:如果 preSum[j] - preSum[i] = k,那么 preSum[i] = preSum[j] - k
这意味着:当我们遍历到位置 j,计算出前缀和 preSum[j] 时,只需要查找之前是否出现过前缀和 preSum[j] - k——如果出现过,就说明存在某个区间 [i+1, j] 的和为 k!
而且,如果 preSum[j] - k 出现过 count 次,说明有 count 个不同的起点都能满足条件!
- 选用:哈希表
{前缀和: 出现次数} - 理由:O(1) 查找某个前缀和出现过几次,一次遍历就能统计所有符合条件的子数组
🔑 模式识别提示:当题目出现"子数组和等于某个值"、"区间和查询",优先考虑"前缀和 + 哈希表"模式
🔑 解法一:暴力枚举(超时但帮助理解)
思路
枚举所有可能的子数组 [i, j],对每个子数组计算和,检查是否等于 k。为了避免 O(n³),在内层循环累加求和,边扫边加,时间降到 O(n²)。
图解过程
示例:nums = [1, 2, 3], k = 3
枚举所有子数组:
i=0: [1]→sum=1 ❌ [1,2]→sum=3 ✅ [1,2,3]→sum=6 ❌
i=1: [2]→sum=2 ❌ [2,3]→sum=5 ❌
i=2: [3]→sum=3 ✅
找到2个:count = 2
Step by Step:
i=0, j=0: sum=1, 1≠3
i=0, j=1: sum=1+2=3, 3==3 ✅ count=1
i=0, j=2: sum=3+3=6, 6≠3
i=1, j=1: sum=2, 2≠3
i=1, j=2: sum=2+3=5, 5≠3
i=2, j=2: sum=3, 3==3 ✅ count=2
结果:2 ✅
示例:nums = [1, 1, 1], k = 2
i=0: [1]→1 ❌ [1,1]→2 ✅ [1,1,1]→3 ❌
i=1: [1]→1 ❌ [1,1]→2 ✅
i=2: [1]→1 ❌
找到2个:count = 2 ✅
Python代码
from typing import List
def subarray_sum_brute(nums: List[int], k: int) -> int:
"""
解法一:暴力枚举
思路:双层循环枚举所有子数组,边扫边加计算和
"""
n = len(nums)
count = 0
for i in range(n): # 枚举起点
current_sum = 0 # 当前子数组和(从 i 开始)
for j in range(i, n): # 枚举终点
current_sum += nums[j] # 边扫边加,避免重复计算
if current_sum == k: # 检查是否等于 k
count += 1
return count
# ✅ 测试
print(subarray_sum_brute([1, 1, 1], 2)) # 期望输出:2
print(subarray_sum_brute([1, 2, 3], 3)) # 期望输出:2
print(subarray_sum_brute([1], 0)) # 期望输出:0
print(subarray_sum_brute([1, -1, 0], 0)) # 期望输出:3
复杂度分析
- 时间复杂度:O(n²) — 两层循环,每个子数组访问一次
- 具体地说:如果 n=2×10^4,需要约 (2×10^4)² / 2 ≈ 2×10^8 次操作,会超时!
- 空间复杂度:O(1) — 只用了几个变量
优缺点
- ✅ 思路直观,容易理解和实现
- ✅ 不需要额外空间
- ❌ 时间复杂度 O(n²),大数据会超时——能不能优化到 O(n)?
⚡ 解法二:前缀和 + 哈希表(最优解 O(n))
优化思路
暴力法的痛点是枚举所有 O(n²) 个子数组。我们用数学变换:如果 preSum[j] - preSum[i] = k,那么 preSum[i] = preSum[j] - k。
这样,遍历到位置 j 时,只需要在哈希表里查"之前是否出现过 preSum[j] - k",如果出现过 count 次,就有 count 个子数组以 j 结尾且和为 k。
💡 关键想法:一边计算前缀和,一边记录每个前缀和出现的次数。遇到新的前缀和时,查找
当前前缀和 - k是否出现过,出现几次就贡献几个子数组。
图解过程
示例:nums = [1, 2, 3], k = 3
初始化:prefix_count = {0: 1} (关键!前缀和为0出现过1次,对应空子数组)
preSum = 0, count = 0
╔════════════════════════════════════════════════════════════╗
║ i=0, num=1 ║
║ preSum = 0 + 1 = 1 ║
║ 查找: preSum - k = 1 - 3 = -2 在 prefix_count 里吗? ║
║ prefix_count = {0:1} → ❌ 不在 ║
║ count = 0 (未增加) ║
║ 记录: prefix_count = {0:1, 1:1} ║
╠════════════════════════════════════════════════════════════╣
║ i=1, num=2 ║
║ preSum = 1 + 2 = 3 ║
║ 查找: preSum - k = 3 - 3 = 0 在 prefix_count 里吗? ║
║ prefix_count = {0:1, 1:1} → ✅ 在!出现过1次 ║
║ count = 0 + 1 = 1 (找到子数组 [1,2]) ║
║ 记录: prefix_count = {0:1, 1:1, 3:1} ║
╠════════════════════════════════════════════════════════════╣
║ i=2, num=3 ║
║ preSum = 3 + 3 = 6 ║
║ 查找: preSum - k = 6 - 3 = 3 在 prefix_count 里吗? ║
║ prefix_count = {0:1, 1:1, 3:1} → ✅ 在!出现过1次 ║
║ count = 1 + 1 = 2 (找到子数组 [3]) ║
║ 记录: prefix_count = {0:1, 1:1, 3:1, 6:1} ║
╚════════════════════════════════════════════════════════════╝
最终答案:count = 2 ✅
解释:
- i=1时找到的子数组 [1,2]:前缀和3 - 前缀和0(空) = 3 ✅
- i=2时找到的子数组 [3]:前缀和6 - 前缀和3([1,2]) = 3 ✅
示例:nums = [1, 1, 1], k = 2
初始化:prefix_count = {0: 1}, preSum = 0, count = 0
╔════════════════════════════════════════════════════════════╗
║ i=0, num=1 ║
║ preSum = 0 + 1 = 1 ║
║ 查找: 1 - 2 = -1 → ❌ 不在 ║
║ count = 0 ║
║ prefix_count = {0:1, 1:1} ║
╠════════════════════════════════════════════════════════════╣
║ i=1, num=1 ║
║ preSum = 1 + 1 = 2 ║
║ 查找: 2 - 2 = 0 → ✅ 在!出现过1次 ║
║ count = 0 + 1 = 1 (找到 [1,1]) ║
║ prefix_count = {0:1, 1:1, 2:1} ║
╠════════════════════════════════════════════════════════════╣
║ i=2, num=1 ║
║ preSum = 2 + 1 = 3 ║
║ 查找: 3 - 2 = 1 → ✅ 在!出现过1次 ║
║ count = 1 + 1 = 2 (找到 [1,1],下标1-2) ║
║ prefix_count = {0:1, 1:1, 2:1, 3:1} ║
╚════════════════════════════════════════════════════════════╝
最终答案:count = 2 ✅
边界用例:nums = [1, -1, 0], k = 0
初始化:prefix_count = {0: 1}, preSum = 0, count = 0
╔════════════════════════════════════════════════════════════╗
║ i=0, num=1 ║
║ preSum = 0 + 1 = 1 ║
║ 查找: 1 - 0 = 1 → ❌ 不在 ║
║ count = 0 ║
║ prefix_count = {0:1, 1:1} ║
╠════════════════════════════════════════════════════════════╣
║ i=1, num=-1 ║
║ preSum = 1 + (-1) = 0 ║
║ 查找: 0 - 0 = 0 → ✅ 在!出现过1次 ║
║ count = 0 + 1 = 1 (找到 [1,-1]) ║
║ prefix_count = {0:2, 1:1} (注意0出现了2次!) ║
╠════════════════════════════════════════════════════════════╣
║ i=2, num=0 ║
║ preSum = 0 + 0 = 0 ║
║ 查找: 0 - 0 = 0 → ✅ 在!出现过2次 ║
║ count = 1 + 2 = 3 (找到 [0] 和 [1,-1,0] 两个!) ║
║ prefix_count = {0:3, 1:1} ║
╚════════════════════════════════════════════════════════════╝
最终答案:count = 3 ✅
三个子数组:[1,-1]、[0]、[1,-1,0]
Python代码
from typing import List
def subarray_sum_hash(nums: List[int], k: int) -> int:
"""
解法二:前缀和 + 哈希表(最优解)
思路:记录每个前缀和出现的次数,查找 preSum - k 是否出现过
"""
# 哈希表:{前缀和: 出现次数}
prefix_count = {0: 1} # ⚠️ 初始化:前缀和0出现过1次(对应空子数组)
presum = 0 # 当前前缀和
count = 0 # 符合条件的子数组个数
for num in nums:
presum += num # 更新前缀和
# 关键步骤:查找 preSum - k 是否出现过
# 如果出现过,说明存在子数组 [i+1, j] 的和为 k
if presum - k in prefix_count:
count += prefix_count[presum - k] # 出现几次就贡献几个子数组
# 记录当前前缀和出现的次数
prefix_count[presum] = prefix_count.get(presum, 0) + 1
return count
# ✅ 测试
print(subarray_sum_hash([1, 1, 1], 2)) # 期望输出:2
print(subarray_sum_hash([1, 2, 3], 3)) # 期望输出:2
print(subarray_sum_hash([1], 0)) # 期望输出:0
print(subarray_sum_hash([1, -1, 0], 0)) # 期望输出:3 (含负数)
print(subarray_sum_hash([0, 0, 0], 0)) # 期望输出:6 (全为0)
print(subarray_sum_hash([3], 3)) # 期望输出:1 (单元素)
复杂度分析
- 时间复杂度:O(n) — 只遍历一次数组,每次哈希表查找和插入都是 O(1)
- 具体地说:如果 n=2×10^4,只需要约 2×10^4 次操作,比暴力法快 1万倍!
- 空间复杂度:O(n) — 哈希表最多存储 n+1 个不同的前缀和
- 典型的"空间换时间"策略
🐍 Pythonic 写法
利用 defaultdict 简化代码:
from collections import defaultdict
from typing import List
def subarray_sum_pythonic(nums: List[int], k: int) -> int:
"""
Pythonic 简洁版:用 defaultdict 自动处理默认值
"""
prefix_count = defaultdict(int)
prefix_count[0] = 1 # 初始化
presum = count = 0
for num in nums:
presum += num
count += prefix_count[presum - k] # 查找并累加
prefix_count[presum] += 1 # 记录
return count
这个写法用 defaultdict(int) 自动处理不存在的键(默认返回 0),代码更简洁。
⚠️ 面试建议:先写清晰版本(解法二)展示思路,如果面试官问"能不能更简洁",再提 Pythonic 写法。面试官更看重你的数学推导和边界处理,而非代码行数。
📊 解法对比
| 维度 | 解法一:暴力枚举 | 解法二:前缀和+哈希 |
|---|---|---|
| 时间复杂度 | O(n²) | O(n) ⭐ |
| 空间复杂度 | O(1) | O(n) |
| 代码难度 | 简单 | 中等(需要理解前缀和变换) |
| 面试推荐 | ⭐ | ⭐⭐⭐ |
| 适用场景 | 小数据集(n<1000) | 大数据集,题目要求O(n)(首选!) |
面试建议:先讲暴力法展示基本思路,然后指出"O(n²)会超时",再引出"前缀和+哈希表"优化。关键是要清晰推导:preSum[j] - preSum[i] = k → preSum[i] = preSum[j] - k,这样展示了数学建模能力!
🎤 面试现场
模拟面试中的完整对话流程,帮你练习"边想边说"。
面试官:请你解决一下这道题,要求尽可能优化。
你:(审题30秒)好的,这道题要求统计和为 k 的连续子数组个数。我的第一个想法是枚举所有子数组,用双层循环,时间复杂度 O(n²)。但数据范围是 2万,O(n²) 约 4 亿次操作,可能会超时。
让我想想能不能优化...子数组和问题我想到了前缀和。如果记 preSum[i] 为前 i 个元素的和,那么区间 [i, j] 的和就是 preSum[j] - preSum[i-1]。我要找 preSum[j] - preSum[i-1] = k,可以变换为 preSum[i-1] = preSum[j] - k。
这样,我遍历到位置 j 时,只需要查找"之前是否出现过前缀和 preSum[j] - k",如果出现过,就说明存在符合条件的子数组!用哈希表记录每个前缀和出现的次数,这样查找是 O(1),总体 O(n)。
面试官:很好,为什么要记录"出现次数"而不只是记录"是否出现"?
你:因为同一个前缀和可能出现多次!比如 [1, -1, 1],前缀和分别是 [1, 0, 1],1 出现了 2 次。如果 k=0,当我到达位置 2(前缀和=1)时,查找 1-0=1 发现出现过 2 次(位置 0 和位置 2 本身之前就有 1 次),但实际上只有 1 个子数组 [1, -1] 满足...等等,我说错了。让我重新理清楚...(修正)应该是:当遍历到位置 i,前缀和是 preSum[i] 时,我查找之前是否出现过 preSum[i] - k。如果出现过 count 次,说明有 count 个不同的位置 j,使得 preSum[i] - preSum[j] = k,也就是有 count 个子数组 [j+1, i] 的和为 k。
面试官:对,思路清晰了。还有一个细节:prefix_count 为什么要初始化为 {0: 1}?
你:这是一个关键的边界处理!{0: 1} 表示"前缀和为 0 出现过 1 次",对应空子数组(前 0 个元素的和是 0)。这样,如果某个位置 i 的前缀和正好等于 k,那么 preSum[i] - k = 0 能在哈希表里找到,表示子数组 [0, i] 的和为 k。如果不初始化,这种情况会漏掉。
面试官:完美!请写一下代码并测试。
你:(写代码)...(测试)用 [1, 2, 3], k=3 测试...结果是 2,正确。再测试 [1, -1, 0], k=0(含负数)...结果是 3,也对。
高频追问
| 追问 | 应答策略 |
|---|---|
| "能不能不用哈希表,空间O(1)?" | 不行。如果不记录前缀和,每次查找 preSum - k 需要遍历之前的元素,退化为 O(n²)。哈希表是用 O(n) 空间换 O(n) 时间的关键 |
| "如果要返回所有符合条件的子数组本身?" | 需要在哈希表中存储 {前缀和: [下标列表]},找到匹配时记录区间 [j+1, i]。空间复杂度仍是 O(n),但常数更大 |
| "如果数组很大,内存放不下?" | 可以分块处理,但这道题必须记录所有可能的前缀和,很难优化空间。或者用双指针,但仅适用于全正数的情况(本题有负数不适用) |
| "为什么不用前缀和数组,而是用一个变量?" | 因为我们只需要"当前前缀和"和"之前出现过的前缀和",不需要存储完整的前缀和数组。用一个变量 presum 边遍历边更新,节省空间 |
🎓 知识点总结
Python技巧卡片 🐍
# 技巧1:字典的 get 方法设置默认值
count = prefix_count.get(presum, 0) + 1 # 如果键不存在,返回 0
# 技巧2:defaultdict 自动处理默认值
from collections import defaultdict
prefix_count = defaultdict(int) # 访问不存在的键自动返回 0
prefix_count[10] += 1 # 无需判断键是否存在
# 技巧3:一行完成查找和累加
count += prefix_count.get(presum - k, 0) # 不存在返回0,存在返回次数
💡 底层原理(选读)
什么是前缀和?为什么它这么有用?
前缀和(Prefix Sum)是一种预处理技巧,通过预先计算"前 i 个元素的累积和",使得任意区间和查询变成 O(1):
preSum[i] = nums[0] + nums[1] + ... + nums[i-1]区间 [i, j] 的和 = preSum[j+1] - preSum[i]为什么区间和 = 两个前缀和的差?
nums = [1, 2, 3, 4] preSum = [0, 1, 3, 6, 10] (preSum[0]=0 表示前0个元素和) 区间 [1, 2] 的和 = nums[1] + nums[2] = 2 + 3 = 5 = preSum[3] - preSum[1] = 6 - 1 = 5 ✅ 本质:preSum[3] 包含了 [0,1,2],preSum[1] 包含了 [0] 两者相减正好剩下 [1,2]前缀和 + 哈希表的威力:
- 前缀和:把"区间和"问题转化为"两个点的差"
- 哈希表:把"枚举所有点对"的 O(n²) 优化为"查找配对"的 O(n)
- 结合起来:O(1) 查询区间和 + O(1) 查找配对 = O(n) 总体!
这个组合在很多题目中都有用:子数组和等于 k、和为 k 的最长子数组、连续子数组和为 k 的倍数等。
算法模式卡片 📐
- 模式名称:前缀和 + 哈希表
- 适用条件:子数组和问题,需要快速查询"某个区间和是否等于某值"
- 识别关键词:"子数组和等于 k"、"连续子数组"、"区间和"、"累积和"
- 核心变换:
区间和 = preSum[j] - preSum[i]→ 转化为"查找之前是否出现过某个前缀和" - 模板代码:
def subarray_sum_pattern(nums, target):
prefix_count = {0: 1} # 初始化:前缀和0出现过1次
presum = count = 0
for num in nums:
presum += num
# 查找 preSum - target 是否出现过
if presum - target in prefix_count:
count += prefix_count[presum - target]
# 记录当前前缀和
prefix_count[presum] = prefix_count.get(presum, 0) + 1
return count
易错点 ⚠️
- 忘记初始化
prefix_count[0] = 1: 这会导致"前缀和正好等于 k"的子数组被漏掉。必须初始化! - 先记录再查找: 如果写成
prefix_count[presum] += 1在前,count += ...在后,会把自己和自己配对(子数组长度为 0),错误!必须先查找再记录 - 混淆"个数"和"是否存在": 哈希表存的是出现次数,不是布尔值。因为同一个前缀和可能出现多次,每次都贡献一个子数组
- 边界用例: 记得测试负数、全为零、单元素等特殊情况
- 前缀和可能很大: 由于元素范围
[-1000, 1000],前缀和范围约[-2×10^7, 2×10^7],Python 的 dict 可以处理任意大小的键,不用担心溢出
🏗️ 工程实战(选读)
这个算法思想在真实项目中的应用,让你知道"学了有什么用"。
- 场景1:金融数据分析 - 股票交易中,找"累计收益率等于某个目标的时间段"。把每日收益率作为数组元素,用前缀和+哈希表快速定位符合条件的交易周期。
- 场景2:用户行为分析 - 在用户活跃度数据中,找"连续 N 天活跃度总和等于某个阈值的时间窗口",用于精准营销或异常检测。
- 场景3:服务器负载均衡 - 分析请求日志,找"请求数总和等于某个容量"的时间段,优化服务器资源调度。
🏋️ 举一反三
完成本课后,试试这些同类题目来巩固知识:
| 题目 | 难度 | 相关知识点 | 提示 |
|---|---|---|---|
| LeetCode 325. 和等于 k 的最长子数组长度 | Medium | 前缀和+哈希表 | 本题变体:不是统计个数,而是找最长的子数组。哈希表存 {前缀和: 第一次出现的下标},更新最大长度 |
| LeetCode 974. 和可被 K 整除的子数组 | Medium | 前缀和+哈希表+同余 | 变换:(preSum[j] - preSum[i]) % k == 0 → preSum[j] % k == preSum[i] % k,记录余数出现次数 |
| LeetCode 523. 连续的子数组和 | Medium | 前缀和+哈希表+同余 | 类似 974,但要求子数组长度 >= 2,需要额外判断 |
| LeetCode 1248. 统计优美子数组 | Medium | 前缀和+哈希表 | 把"奇数个数等于 k"转化为前缀和问题:奇数个数的前缀和 |
📝 课后小测
试试这道变体题,不要看答案,自己先想5分钟!
题目:给定一个整数数组和一个整数 k,找出和等于 k 的最长连续子数组的长度。如果不存在返回 0。
例如:nums = [1, -1, 5, -2, 3], k = 3,最长子数组是 [1, -1, 5, -2, 3] 或 [-2, 3, ...],长度是...等等,让你自己算!
💡 提示(实在想不出来再点开)
仍然用前缀和+哈希表,但这次哈希表存 {前缀和: 第一次出现的下标}。当找到 preSum - k 时,计算当前下标与第一次出现下标的距离,更新最大长度。
关键:要存"第一次出现的下标",因为我们要最长的子数组,所以起点越早越好!
✅ 参考答案
def max_subarray_length(nums: List[int], k: int) -> int:
"""和等于 k 的最长子数组长度"""
prefix_index = {0: -1} # {前缀和: 第一次出现的下标},-1表示空子数组
presum = 0
max_len = 0
for i, num in enumerate(nums):
presum += num
# 查找 preSum - k 是否出现过
if presum - k in prefix_index:
# 计算长度:当前下标 - 第一次出现的下标
max_len = max(max_len, i - prefix_index[presum - k])
# 只记录第一次出现的下标(保证最长)
if presum not in prefix_index:
prefix_index[presum] = i
return max_len
# 测试
print(max_subarray_length([1, -1, 5, -2, 3], 3)) # → 5 ([1,-1,5,-2,3])
print(max_subarray_length([1, 2, 3], 3)) # → 2 ([1,2] 或 [3])
核心思路:哈希表存第一次出现的下标,因为要最长子数组,起点越早越好。找到匹配时,计算当前位置与第一次出现位置的距离,更新最大值。O(n) 时间,O(n) 空间!
如果这篇内容对你有帮助,推荐收藏 AI Compass:github.com/tingaicompa… 更多系统化题解、编程基础和 AI 学习资料都在这里,后续复习和拓展会更省时间。