题目
给定二进制数组 nums(只含 0 和 1),找出含有相同数量 0 和 1 的最长子数组,返回其长度。
核心思路:0 看作 -1,把问题转化为「和为 0 的最长子数组」
直接数 0 和 1 的个数很难,但做一个等价替换就豁然开朗:
- 遇到
1→ 记+1 - 遇到
0→ 记-1
记 prefix(i) 为前 i 项的和,那么区间 [j+1, i] 的和为:
sum=prefix(i)−prefix(j)
区间和为 0 ⟺ 这段里 +1 和 -1 一样多 ⟺ 0 和 1 数量相等。
于是问题变成:找两个下标 i > j,使 prefix(i) == prefix(j),且 i - j 最大。这就是经典的「前缀和 + 哈希表」。
两个关键细节(笔记里也标了)
① 为什么要 map.put(0, -1)
当满足条件的子数组从头开始 时(即 prefix(i) == 0),它对应的「前一个位置」是数组开头之前,下标记为 -1。这样长度才是 i - (-1) = i + 1,而不是漏掉从 0 开始的情况。例如 nums = [0,1],i=1 时 sum=0,答案 = 1-(-1) = 2,正确。
② 遇到出现过的前缀和,只算长度、不能覆盖下标
求的是最长 ,同一个前缀和出现的位置越早,跨度越大。所以要保留首次出现的下标:
if (map.containsKey(sum)) {
ans = Math.max(ans, i - map.get(sum));
} else {
map.put(sum, i); // 只有没出现过才写,保证存的是最早下标
}
完整 Java 代码
class Solution {
public int findMaxLength(int[] nums) {
Map<Integer, Integer> first = new HashMap<>();
first.put(0, -1); // 前缀和 0 出现在"数组开始前"
int sum = 0, ans = 0;
for (int i = 0; i < nums.length; i++) {
sum += (nums[i] == 0 ? -1 : 1); // 0 视作 -1
if (first.containsKey(sum)) {
ans = Math.max(ans, i - first.get(sum));
} else {
first.put(sum, i); // 只记录最早出现的下标
}
}
return ans;
}
}
复杂度:时间 O(n),一次遍历;空间 O(n),最坏情况 n 个前缀和互不相同。
手推一遍示例
nums = [0,1,1,0,1,1,1,0]
| i | numsi | sum | map 状态(关键值) | 更新 ans |
|---|---|---|---|---|
| 初始 | --- | 0 | {0: -1} |
--- |
| 0 | 0 | -1 | 加 {-1: 0} |
--- |
| 1 | 1 | 0 | 命中 0 → -1 | 1-(-1) = 2 |
| 2 | 1 | 1 | 加 {1: 2} |
--- |
| 3 | 0 | 0 | 命中 0 → -1 | 3-(-1) = 4 |
| 4 | 1 | 1 | 命中 1 → 2 | 4-2 = 2 |
| 5 | 1 | 2 | 加 {2: 5} |
--- |
| 6 | 1 | 3 | 加 {3: 6} |
--- |
| 7 | 0 | 2 | 命中 2 → 5 | 7-5 = 2 |
答案 4 ,对应子数组 [1,1,0,1,1,0] 中的 [1,0,1,1,0]?------不对,取的是 i=3 处长度 4,即 [0,1,1,0](下标 0~3),恰好 2 个 0、2 个 1。