525. 连续数组
难度:中等 | 主题:数组、哈希表、前缀和
题目
给定一个二进制数组 nums(只包含 0 和 1),找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
示例
nums = [0,1] → 输出 2
nums = [0,1,0] → 输出 2([0,1] 或 [1,0] 都行)
思路
先讲个故事:记账本里的收支平衡
你有一本账本,每天记一笔:收入记 +1,支出记 -1。你想找最长的一段连续记录,使得收支平衡(总和为 0)。
但账本上只有”收入”和”支出”的符号,没有数字。你把”支出”看成 -1、“收入”看成 +1——问题变成了「找和为 0 的最长子数组」。
引导式推导:从暴力到前缀和
暴力法:枚举所有子数组,统计 0 和 1 的个数。O(n²) 太慢。
优化:计数类问题 → 前缀和。
关键转化:
-
0 → -1
-
1 → +1
-
问题变成:找和为 0 的最长连续子数组
前缀和相等意味着什么?
prefix[i] = sum(nums[0..i])
子数组 [j+1..i] 的和 = prefix[i] - prefix[j]
和为 0 → prefix[i] == prefix[j]
所以两个位置前缀和相等,说明中间那段 0 和 1 一样多。
graph LR A["原始数组 [0,1,0]"] --> B["转化 [ -1, +1, -1]"] B --> C["计算前缀和"] C --> D["找相等前缀和的最远距离"]
为什么用哈希表? 要快速知道某个前缀和最早出现在哪。遍历时,如果当前前缀和之前出现过,中间这一段就是候选答案。只保留第一次出现的位置,才能确保距离最远。
两层理解
第一层(为什么 0→-1):0 和 1 数量相等 → 0 的个数 = 1 的个数 → 把 0 当 -1 则总和为 0。这是把计数问题转化为求和问题的关键 trick。
第二层(为什么前缀和相等就够了):
-
前缀和记录的是到当前位置的”净余额”
-
两个位置净余额相同 → 中间那段操作恰好抵消
代码
def findMaxLength(self, nums):
# 哈希表:key=前缀和, value=第一次出现的下标
# 初始化 {0: -1} —— 空前缀,表示还没开始遍历时前缀和为0
map = {0: -1}
prefix = 0 # 当前前缀和
max_len = 0 # 最长长度
for i, num in enumerate(nums):
prefix += 1 if num == 1 else -1 # 1→+1, 0→-1
if prefix in map:
# 之前出现过相同前缀和 → 中间子数组和为0
max_len = max(max_len, i - map[prefix])
else:
# 第一次出现 → 记下来(不更新已有位置!)
map[prefix] = i
return max_len
复杂度
| 指标 | 值 | 解释 |
|---|---|---|
| 时间 | O(n) | 一次遍历,哈希操作 O(1) |
| 空间 | O(n) | 哈希表最多存 n 个前缀和 |
实战考量
频率分析
出现在:美团/字节二面左右,约 15% 的前缀和类常会考到此题。核心考察能不能把 0/1 计数转化为前缀和问题。
延伸思考
Q:为什么非要 0→-1?保持 0 和 1 不行吗?
A:不行。前缀和的核心是求和,0 在加法里是”空气”——加 0 不改变和,无法反映 0 和 1 的平衡关系。把 0 变成 -1,每次遇到 0 和 1 就相互抵消,完美反映平衡状态。
Q:为什么只需要存第一次出现的位置?
A:要最大化长度。相同前缀和出现多次时,第一次和最后一次的距离最远。只留第一次就能保证找到最远的。
Q:初始化 {0: -1} 是干什么的?
A:处理从开头就满足条件的子数组。比如 [0,1],遍历完 prefix=0,此时 map 里有 0→-1,长度为 i - (-1) = 1 - (-1) = 2。没有这个初始化,从开头开始的子数组全算不出来。
Q:和 560和为K的子数组 的区别?
A:560 求个数(哈希表存次数),这题求最长长度(哈希表存第一次出现的位置)。一个是计数,一个是测距。
易错点
-
不初始化
{0: -1}→ 从开头开始的子数组算不出来 -
相同前缀和不更新位置(只保留第一次)
-
0→-1 这步转化想不通就卡住
生活类比
收支平衡 → 前缀和 → 哈希表
你站在一个路口左侧,每走一步,收入进账 +1,支出出账 -1。
走到某个位置回头一看,发现净余额和某个之前的位置相同——
说明从那里到这里的每一步,收入和支出正好抵消。
记下每个净余额第一次出现的位置,以后每次路过就看一下:
“从这里到那里,我白忙活了一段——收支平衡。“
相关题目
| 题目 | 关系 |
|---|---|
| 560和为K的子数组 | 同前缀和题型,求个数 vs 求最长 |
| 53最大子数组和 | 不同思路(Kadane),但也是子数组求和 |
→ 返回题单:LeetCode学习路线图 > 一、数组与哈希(含双指针、矩阵)
速记卡(面试闪卡)
Q1:一句话讲清「525. 连续数组」到底是什么?
A:525 题求含相同数量 0 和 1 的最长连续子数组,靠 0→-1 转化与前缀和+哈希表解决。
Q2:题目 —— 怎么理解?
A:像找最长「收支平衡」段:给二进制数组,求含相同数量 0 和 1 的最长连续子数组长度。示例 [0,1,0]→2。把计数问题变成求和问题。
Q3:思路 —— 怎么理解?
A:像记账本收支抵消:0→-1、1→+1,问题变「找和为 0 的最长子数组」;前缀和相等说明中间段 0/1 一样多。哈希表记每个前缀和第一次出现的位置,保证距离最远。
Q4:代码 —— 怎么理解?
A:像边走边记净余额:哈希表 {前缀和: 第一次下标},初始化 {0:-1}(处理从开头就平衡);遇到相同前缀和就更新最大长度,否则记下第一次位置(不更新!)。
Q5:复杂度 —— 怎么理解?
A:像一次走完就够:时间 O(n) 一次遍历、哈希 O(1);空间 O(n) 存前缀和。易错:不初始化 {0:-1} 开头段算不出、相同前缀和不更新、0→-1 转化想不通就卡。
Q6:核心速记主线有哪些?
-
题意:最长含等量 0/1 的连续子数组
-
关键:0→-1 转化,变找和为 0 的最长子数组
-
前缀和相等即中间段平衡;哈希表存第一次位置
-
复杂度:时间 O(n)、空间 O(n);记得初始化 {0:-1}
口诀
A:连续数组五二五,零一等量最长求;
零变负一转求和,前缀相等中间兜;
哈希记首次位置,初始化零负一留;
一遍遍历 O(n),收支平衡最长收。