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),收支平衡最长收。

相关链接