- 教程
- 文档
【免费下载链接】LogicStack-LeetCode
公众号「宫水三叶的刷题日记」刷穿 LeetCode 系列文章源码
导读
本文基于「宫水三叶的刷题日记」刷穿 LeetCode 系列的第 485 篇题解,完整讲解 LeetCode 485「最大连续 1 的个数」这道高频简单题:给定一个只包含 0 和 1 的二进制数组,如何用 O(n) 时间、O(1) 空间求出最长连续 1 段的长度。全文从暴力思路出发,逐步收敛到双指针扫描的核心思想,并给出 Java、C++、Python、TypeScript 四种可直接提交的完整实现,最后结合仓库中同构的延伸题目(如 1446. 连续字符、2760. 最长奇偶子数组、1004. 最大连续1的个数 III)说明这套「找最远合法右端点并整体跳转」的双指针范式可以如何迁移复用。
一、题目描述与约束
原题文档位于仓库的 485. 最大连续 1 的个数(简单).md,题目本身非常简洁:
给定一个二进制数组,计算其中最大连续 1 的个数。
示例:
输入:[1,1,0,1,1,1] 输出:3 解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3。题目的约束条件有两个,它们直接决定了算法的选择空间:
- 输入的数组只包含
0和1; - 输入数组的长度是正整数,且不超过 $10^4$。
因为数组元素只有 0/1 两种取值、长度上限也只有 $10^4$,这道题的算法空间很宽松。但作为双指针入门题,它的价值在于让读者掌握「如何把连续段整体处理、如何跳过必然不优的候选起点」,这也是它在系列中被归类为 Tag「双指针」的原因——该归类可对照仓库的 双指针题单 查看,其中收录了包括 485 在内的数十道双指针题目及对应题解链接。
二、思路推导:从暴力枚举到双指针
2.1 暴力做法
最朴素的想法是枚举每一个可能的起点,再向后扩展统计连续 1 的长度:
- 外层循环枚举左端点
i; - 内层循环从
i开始向右,只要nums[j] == 1就一直累加长度; - 每次把当前段长度与全局答案
ans取最大值。
暴力做法的时间复杂度为 O(n²),最坏情况(如数组全为 1)下会枚举出 O(n²) 个候选段。虽然本题 $n \le 10^4$ 勉强可行,但完全没有必要——因为其中大量候选段是「必不可能是最长」的,可以被整体跳过。
2.2 双指针的核心观察
原文档给出的双指针解法建立在这样一个观察上:
对于一个确定的左端点l,如果它是 1,那么以它开头的最长连续 1 段,其右边界是唯一确定的——即向右扫描遇到的第一个 0(或数组越界)。
设r为这个最远右边界,则[l, r)区间内全是 1,段长度为r - l。此时可以断言:
以
[l + 1, r]中任意位置k作为左端点,都不需要再检查。
原因是:从k出发的连续 1 段,其右边界不可能越过r(因为nums[r]是 0 或越界,任何以k为起点的连续 1 段都会被r位置的 0 截断),因此任何这类段的长度至多为r - k < r - l,必然不大于已经得到的r - l。
因此扫描流程可以设计为:
- 令
l从数组头开始扫描; - 若
nums[l] == 0,说明它不可能是连续 1 段的开头,l直接右移跳过; - 若
nums[l] == 1,令r = l,向右找到第一个不满足「r在界内且nums[r] == 1」的位置,此时[l, r)全为 1,长度r - l更新ans; - 令
l = r + 1,从该位置继续处理(r位置本身是 0 或越界,不可能作为新段的开头)。
这样每个位置最多被l和r各访问常数次,整个扫描是严格线性的 O(n)。
三、四种语言的完整实现
原文档为这道题提供了 Java、C++、Python、TypeScript 四种可直接提交的实现,以下代码完整保留原文档写法。
Java
class Solution { public int findMaxConsecutiveOnes(int[] nums) { int n = nums.length, ans = 0; for (int l = 0; l < n; ) { if (nums[l] == 0 && ++l >= 0) continue; int r = l; while (r < n && nums[r] == 1) r++; ans = Math.max(ans, r - l); l = r + 1; } return ans; } }C++
class Solution { public: int findMaxConsecutiveOnes(vector<int>& nums) { int n = nums.size(), ans = 0; for (int l = 0; l < n;) { if (nums[l] == 0 && ++l < n) continue; int r = l; while (r < n && nums[r] == 1) ++r; ans = max(ans, r - l); l = r + 1; } return ans; } };Python
class Solution: def findMaxConsecutiveOnes(self, nums: List[int]) -> int: n, ans = len(nums), 0 l, r = 0, 0 while l < n: if nums[l] == 0: l += 1 continue r = l while r < n and nums[r] == 1: r += 1 ans = max(ans, r - l) l = r + 1 return ansTypeScript
function findMaxConsecutiveOnes(nums: number[]): number { let n = nums.length, ans = 0; for (let l = 0, r = 0; l < n; ) { if (nums[l] == 0 && ++l >= 0) continue; r = l; while (r < n && nums[r] == 1) r++; ans = Math.max(ans, r - l); l = r + 1; } return ans; };四、代码细节解读:边界安全与跳转逻辑
这几段代码里有几处值得展开的细节,理解它们能避免在真实提交时踩坑:
if (nums[l] == 0 && ++l >= 0) continue;(Java/TypeScript 版):由于l始终是非负整数,++l >= 0恒为真,所以这行的实际效果等价于「l先自增,然后continue回到循环条件判断」。当l恰好是最后一个元素且为 0 时,++l后l == n,外层for条件l < n直接判假退出,不会发生越界访问。if (nums[l] == 0 && ++l < n) continue;(C++ 版):写法略有差异,多带了一个界内判断。当nums[l] == 0且++l后仍小于n时继续循环;若++l后l == n(即最后一个元素是 0),则不continue,而是进入下面的逻辑——此时r = l且l == n,while (r < n && nums[r] == 1)因为r == n不成立而不执行,ans不会被错误更新,随后l = r + 1使外层条件判假退出。两种写法殊途同归,都保证了数组末端的 0 能被安全跳过。while (r < n && nums[r] == 1) r++;:循环停止时有两种可能——r越界,或者nums[r] == 0。无论哪种,[l, r)区间内的元素全部为 1,段长度为r - l。注意区间是左闭右开的,因此更新用的是r - l而非r - l + 1。l = r + 1:r位置要么是 0、要么越界,前者不可能成为新连续段的起点(下一次循环会被跳过),后者说明数组已扫描完毕。因此直接从r + 1继续是安全且最优的,这正是把 O(n²) 压缩到 O(n) 的关键跳转。
五、复杂度分析
- 时间复杂度:O(n)。
l与r在整个扫描过程中只向右移动,每个元素最多被访问常数次; - 空间复杂度:O(1)。只使用了常数个整型变量,没有借助额外数组或哈希结构。
在原文档中,这两项复杂度结论直接标注在四段代码之后,是本题作为「双指针模板题」最值得记忆的性质:线性时间、常数空间。
六、正确性论证与边界情况
6.1 为什么跳过的候选起点不会影响答案
这是本题唯一需要严谨论证的点。设当前左端点为l(nums[l] == 1),其最远合法右边界为r。对于任意k \in [l + 1, r],若把k作为左端点,那么它能扩展出的连续 1 段必然被nums[r] == 0(或越界)截断,段长至多为r - k,严格小于r - l。因此跳过这些起点只丢弃了「必不可能是全局最长」的方案,答案的准确性不受影响。这正是双指针能在线性时间内完成扫描的理论依据。
6.2 典型边界情况
- 全 0 数组:如
[0,0,0],所有l都被跳过,ans保持初始值 0,正确; - 全 1 数组:如
[1,1,1],l = 0时r一路走到n,ans = 3,一次扫描完成; - 单元素数组:
[0]得 0,[1]得 1,均由循环逻辑自然覆盖; - 连续 1 段出现在数组末尾:如
[0,1,1],r越界时while正常停止,长度按r - l计算依然正确,无需特判。
七、仓库延伸:同构双指针题目的迁移
「固定左端点找最远合法右端点、然后整体跳转」这一范式在刷穿 LeetCode 系列中反复出现,仓库里有三题与 485 高度同构,可作为巩固练习:
1446. 连续字符(简单):把「连续 1」换成「连续相同字符」,同样是外层枚举段起点、内层用右指针找最远相同字符边界、按段长更新答案后整体跳转。代码骨架与 485 几乎一一对应,是验证双指针模板掌握度的最佳练习。
2760. 最长奇偶子数组(简单):题目改为找「先偶后奇交替、且元素不超过 threshold」的最长子数组。该题解完整论证了「跳过
[i, j]之间作为左端点的方案,只会漏掉必不可能是最长子数组的方案」,并把朴素 O(n²) 优化到 O(n)——这正是 485 中「跳转」正确性论证的进阶版,值得精读。1004. 最大连续1的个数 III(中等):允许把最多 K 个 0 翻转为 1,求最长连续 1 段。题解给出了「动态规划(TLE)→ 前缀和 + 二分 → 双指针滑动窗口」的三步递进:在 485 的扫描思路上引入窗口内 0 的计数约束,左端点在窗口内 0 的个数超过 K 时才右移。从 485 到 1004 正好展示了双指针从「固定语义段扫描」到「带约束滑动窗口」的升级路径,也是理解「根据数据范围选择算法」的绝佳案例。
上述题目的题解链接均可在仓库的 双指针题单 与 滑动窗口题单 中定位到。
八、小结
LeetCode 485「最大连续 1 的个数」虽然难度为简单,但它是双指针入门阶段的标杆题:
- 核心技巧:用左右双指针把连续 1 段作为整体处理,借助「中间起点必不更优」的论证实现整体跳转;
- 实现要点:注意左闭右开区间的长度计算(
r - l),以及数组末端 0 的越界安全处理(各语言写法略有差异,但语义一致); - 复杂度特征:O(n) 时间、O(1) 空间,是面试中追求线性扫描时的标准答案;
- 迁移价值:同一套「找最远合法右端点 + 整体跳转」的框架,可直接迁移到连续字符、奇偶交替子数组、以及带翻转次数的滑动窗口变体中。
掌握本题后,建议按上述延伸题目依次练习,并在提交时对照仓库中四语言代码检查边界处理,即可完整吃透这一双指针基础范式。
- 教程
- 文档
【免费下载链接】LogicStack-LeetCode
公众号「宫水三叶的刷题日记」刷穿 LeetCode 系列文章源码
相关推荐
LeetCode-Go 题解精讲:485. Max Consecutive Ones 最大连续 1 的个数
LeetCode Go 题解精讲:485. Max Consecutive Ones 最大连续 1 的个数 导读 本文围绕 LeetCode 第 485 题「M
示例工程连续数组(LeetCode 525):前缀和 + 哈希表求解最长 0/1 个数相等的子数组
连续数组(LeetCode 525):前缀和 + 哈希表求解最长 0/1 个数相等的子数组 导读 本文围绕「刷穿 LeetCode」系列第 525 题的经典题解
教程文档LeetCode 485 最大连续 1 的个数(Max Consecutive Ones)多语言解法全解析:暴力、单次遍历与常见陷阱
LeetCode 485 最大连续 1 的个数(Max Consecutive Ones)多语言解法全解析:暴力、单次遍历与常见陷阱 导读 本文围绕 LeetC
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考