连续数组
nums 含 0/1,最长连续子数组 0/1 个数相等。
nums 含 0/1,最长连续子数组 0/1 个数相等。
当前前缀值是什么,需要查询哪一个过去前缀?
前缀和 count0-count1;map 最早出现某和→最长。
先说结论:这道题到底解决什么
怎样从“nums 含 0/1,最长连续子数组 0/1 个数相等。”推导出 前缀和 · 连续数组,并证明每次状态变化都不会漏掉答案?
中心结论:前缀和 count0-count1;map 最早出现某和→最长。
- 1.暴力方案在哪里重复计算,为什么仍然是正确基线?
- 2.当前前缀值是什么,需要查询哪一个过去前缀?
- 3.不变量“diff 相同则中间段 0/1 平衡。”为什么能保证算法安全前进?
完整题目与题意拆解
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
在本站主例中,nums 含 0/1,最长连续子数组 0/1 个数相等。
算法最终需要得到或观察:最长长度 6(0..5)。
- • 输入:nums 含 0/1,最长连续子数组 0/1 个数相等。
- • 机器需要维护:prefix diff、map 最早下标。
- • 最终可观察结果:最长长度 6(0..5)。
先看清算法到底要维护什么
先建立输入、目标、输出和第一批状态,不急着进入模板。
区间和 = prefix[right+1] - prefix[left]
第一层方案:暴力做法
每次重新计算区间会重复累加;前缀状态把区间信息变成两个历史状态的差或关系。
暴力方案的价值是确认题意并提供正确性基线。它通常会覆盖所有候选, 但没有保存已经确认的信息,因此同一状态会被重新计算。
重复工作究竟发生在哪里
把重复读取或重复搜索的区域明确标出,再决定优化必须保存什么。
区间和 = prefix[right+1] - prefix[left]
整体地图:先做什么,再做什么
- 1建模把输入翻译成“前缀累计仪”,明确答案需要观察什么。
- 2状态只维护 prefix diff、map 最早下标。
- 3转移每一步按照 前缀和 count0-count1;map 最早出现某和→最长。
- 4收尾读取 最长长度 6(0..5)。,并复核边界与复杂度。
前缀累计仪:核心概念
把“某段区间”改写成“两个前缀之间的关系”。
这道题不是为了记住一个组件名称,而是为了让状态具有可解释的语义:prefix diff、map 最早下标。
- • diff 相同则中间段 0/1 平衡。
建立“前缀累计仪”心智模型
用主例建立核心状态,先预测下一步,再公开正确分支和理由。
区间和 = prefix[right+1] - prefix[left]
核心机制:状态如何一步步变化
0 和 1 的数量相同可以转化为两者数量相差为 0,如果将 0 看作为 -1,那么原题转化为求最长连续子数组,其元素和为 0 。又变成了区间内求和的问题,自然而然转换为前缀和来处理。假设连续子数组是 [i,j] 区间,这个区间内元素和为 0 意味着 prefixSum[j] - prefixSum[i] = 0,也就是 prefixSum[i] = prefixSum[j]。不断累加前缀和,将每个前缀和存入 map 中。一旦某个 key 存在了,代表之前某个下标的前缀和和当前下标构成的区间,这段区间内的元素和为 0 。这个区间是所求。扫完整个数组,扫描过程中动态更新最大区间长度,扫描完成便可得到最大区间长度,即最长连续子数组。
前缀和 count0-count1;map 最早出现某和→最长。
执行过程中持续维护:prefix diff、map 最早下标。
正确性依赖以下不变量:diff 相同则中间段 0/1 平衡。
面试时可以压缩为:前缀和+哈希 O(n)。
落到当前题,执行机制可以压缩为:前缀和 count0-count1;map 最早出现某和→最长。每一次更新都必须保持核心不变量,而不是只让样例碰巧得到正确答案。
一次状态转移为什么成立
集中观察一次选择、计算和状态写回,让变量变化与原因同时出现。
累加前缀
正确性证明:为什么不会漏答案
初始化:算法开始时,全部合法候选仍在状态表示范围内;diff 相同则中间段 0/1 平衡。
保持:执行“前缀和 count0-count1;map 最早出现某和→最长。”时,只删除已经能证明不可能的候选,并把新信息写回 prefix diff、map 最早下标。
终止:没有待处理状态或达到命中条件时,当前可观察结果就是“最长长度 6(0..5)。”。
完整执行过程
- 1题目与输入nums 含 0/1,最长连续子数组 0/1 个数相等。 因为:前缀和 count0-count1;map 最早出现某和→最长。
- 2prefix[i] = 前 i 个元素和O(n) 建前缀和。 因为:把区间查询变 O(1)。
- 3prefix[1] = prefix[0] + nums[0] = 0扩展前缀表。 因为:定义式累加。
- 4prefix[2] = prefix[1] + nums[1] = 1扩展前缀表。 因为:定义式累加。
- 5prefix[3] = prefix[2] + nums[2] = 1扩展前缀表。 因为:定义式累加。
- 6sum(0,2) = 1区间查询完成。 因为:前缀和标准应用。
- 7prefix[i] = 前 i 个元素和O(n) 建前缀和。 因为:把区间查询变 O(1)。
- 8收尾与复杂度最长长度 6(0..5)。 因为:时间 O(n) · 空间 O(1)。前缀和 count0-count1;map 最早出现某和→最长。
从输入完整走到可观察结果
从主例第一帧运行到答案,时间轴始终显示当前状态、因果解释和下一步。
区间和 = prefix[right+1] - prefix[left]
把动画和 Go 代码逐行对应
代码窗口不会按容易漂移的固定数字行号硬绑动画,而是根据当前语义阶段, 在完整 Go 实现中定位选择、计算、写回或返回分支。当前变量与高亮行一起变化。
让每个动作都落到 Go 分支
重新执行关键分支,只显示当前代码附近窗口,并说明这行为什么在此刻运行。
区间和 = prefix[right+1] - prefix[left]
完整 Go 提交代码与最小测试
func findMaxLength(nums []int) int {
dict := map[int]int{}
dict[0] = -1
count, res := 0, 0
for i := 0; i < len(nums); i++ {
if nums[i] == 0 {
count--
} else {
count++
}
if idx, ok := dict[count]; ok {
res = max(res, i-idx)
} else {
dict[count] = i
}
}
return res
}
func max(a, b int) int {
if a > b {
return a
}
return b
}func main() {
// 1. 主例
// 输入:mode="prefix-sum-range", nums=[0,1,0]
// 期望:最长长度 6(0..5)。
//
// 2. 失败 / 未命中
// 检查:把 0 变 -1 可化成和为 0 最长。
//
// 3. 边界
// 空输入、单元素、最小合法规模,以及答案恰好落在边界的情况。
//
// 4. 迁移
// LC560 和为 K;LC974 整除 K
}Go 参考实现基于 halfrost/LeetCode-Go 的 MIT 许可代码整理,并按本站教学结构补充解释与动画映射。
正确性与复杂度
执行过程中只保留仍可能影响答案的状态。前缀和 count0-count1;map 最早出现某和→最长。
额外状态主要用于维护:prefix diff、map 最早下标。
- • diff 相同则中间段 0/1 平衡。
最容易写错的地方
把 0 变 -1 可化成和为 0 最长。
必须额外检查空输入、单元素、未命中或不可达情况,以及恰好落在边界的输入。
最后复盘:带走逻辑链
- 1题意nums 含 0/1,最长连续子数组 0/1 个数相等。
- 2重复每次重新计算区间会重复累加;前缀状态把区间信息变成两个历史状态的差或关系。
- 3优化前缀和 count0-count1;map 最早出现某和→最长。
- 4证明diff 相同则中间段 0/1 平衡。
- 5复杂度时间 O(n),空间 O(1)
- • LC560 和为 K
- • LC974 整除 K