← 返回题目列表

0 和 1 数量相等的最长子数组,为什么把 0 看成 -1?

高频 中等 第 2 / 20 题 更新于 2026/08/03
前缀和哈希表0-1数组

简化版

把数组里的 0 看成 -11 仍然看成 1

这样一个子数组中 0 和 1 数量相等,就等价于这段转换后的和为 0

遍历时维护前缀和,记录每个前缀和第一次出现的位置;如果同一个前缀和再次出现,中间这段和为 0,更新最长长度。

详细版

这题的关键转化是平衡计数。

遇到 1,前缀和加 1;遇到 0,前缀和减 1。如果两个位置的前缀和相同,说明它们之间的增减量抵消,也就是 0 和 1 数量相等。

为了得到最长长度,哈希表里应该保存某个前缀和第一次出现的位置。再次遇到相同前缀和时,用当前位置减第一次位置。

初始化 map.set(0, -1),表示从开头到当前位置如果前缀和为 0,也是一段合法子数组。

时间复杂度 O(n),空间复杂度 O(n)

完整版教学

一、为什么要把 0 转成 -1

原数组只有 0 和 1,直接求和只能知道 1 的数量,不能同时反映 0 的数量。把 0 当成 -1 后,子数组和等于 0 就表示 1 的贡献和 0 的负贡献刚好抵消。这样“数量相等”就被转换成“区间和为 0”。

记忆钩子:平衡类 0/1 问题,把 0 变成 -1,平衡就变成和为 0。

二、同前缀和为什么表示中间和为 0

prefix[i] 是到当前位置的累计平衡值。如果 prefix[j] == prefix[i],那么 (i, j] 这一段的增量为:

prefix[j] - prefix[i] = 0

增量为 0,说明这一段里的 10 数量相同。这个结论不依赖段内顺序,只看总贡献。

三、为什么记录第一次出现的位置

题目要最长子数组。同一个前缀和出现多次时,当前位置固定,越早的出现位置能形成越长的区间。因此哈希表里只在第一次看到某个前缀和时记录位置,后面不要覆盖。

前缀和第一次位置再次位置长度
0-156
1143

保留最早位置,才能得到最长答案。

四、带数字推演

数组 [0,1,0,1,1] 转换成 [-1,1,-1,1,1]

初始 sum=0 at -1
i=0: sum=-1,记录 -1 -> 0
i=1: sum=0,之前在 -1,长度 2
i=2: sum=-1,之前在 0,长度 2
i=3: sum=0,之前在 -1,长度 4
i=4: sum=1,记录 1 -> 4

最长长度是 4,对应 [0,1,0,1]

五、代码模板

实现如下:

function findMaxLength(nums) {
  const firstIndex = new Map()
  firstIndex.set(0, -1)
  let sum = 0
  let ans = 0
  for (let i = 0; i < nums.length; i++) {
    sum += nums[i] === 1 ? 1 : -1
    if (firstIndex.has(sum)) {
      ans = Math.max(ans, i - firstIndex.get(sum))
    } else {
      firstIndex.set(sum, i)
    }
  }
  return ans
}

初始化 0 -> -1 是为了处理从下标 0 开始就平衡的情况。

六、常见误区与追问

  • 误区:只统计 1 的个数。 题目要求 0 和 1 数量相等,必须同时表达两者差值。
  • 误区:重复前缀和时覆盖位置。 覆盖会丢掉最早位置,导致最长长度变短。
  • 误区:忘记初始化 0 到 -1。 从数组开头开始的合法子数组会被漏掉。
  • 追问:为什么不是滑动窗口? 数组转换后有正有负,窗口和不具备单调性。
  • 追问:能扩展到其他平衡字符吗? 可以,核心是把不同类别映射成可抵消的状态,但多类别通常要用向量状态。

这些点考的是“平衡转前缀状态”的能力。

七、加强记忆

这题记成“0 变 -1,同和夹出平衡段”。前缀和相同表示中间净增量为 0,也就是 0 和 1 数量抵消。为了最长,哈希表只保留第一次出现的位置。初始化 sum=0-1,可以自然覆盖从开头开始的答案。