最大的连续1的个数III
- 滑动窗口使用的条件:
- 题目描述:
- 解题思路:
- AI的解题思路
- 最终代码实现
- 关键逻辑注释
我们先来说明一下什么时候会去使用到滑动窗口来解决问题:
当题目满足以下3个条件时,用滑动窗口:
滑动窗口使用的条件:
1:题目求的是「连续的子数组/子串」
1. 比如:连续的一段数字、连续的字符串片段
2. ❌ 如果题目允许不连续(如子序列),不能用滑动窗口
2:题目要求的是「最大长度」或「最小长度」
比如:
- 最长无重复子串(最大长度)
- 最短的满足和≥target的子数组(最小长度)
3:窗口的扩张和收缩有明确的规则
- 扩张(右指针右移):探索新元素,更新状态(如字符计数、和值)
- 收缩(左指针右移):当窗口不满足条件时调整(如重复字符、和超过target)
题目描述:
1004.最大的连续1的个数III
给定一个二进制数组 nums 和一个整数 k,假设最多可以翻转 k 个 0 ,则返回执行操作后 数组中连续 1 的最大个数 。
示例 1:
输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1
0 <= k <= nums.length
解题思路:
我们可以将这一道转化为:
找出最长的连续1的子数组,在这个子数组中0的个数不超过k个:
接下来我使用一个特殊的总结归纳方法来解除这道题目,在下面的一问一答之间,穿插好题目的讲解
第一步:用自己的话,描述一下眼前的这个问题,它是一个怎么样的问题?
这个问题的目的是为了在这个只有0和1的数组中,找出最大的连续1的子数组,同时在这个子数组中的0不超过k个,此时刚好都满足了滑动窗口的条件。
第二步:这个问题有哪些特征,能让我们去判断,它属于这一类的问题?
- 第一个特征:题目要求的是一个连续的子数组
- 第二个特征:题目要求的是最大的连续的为1的子数组
- 第三个特征:题目要求在这个子数组中0的个数不超过k个
题目的要求就需要使用到同向双指针去动态调整子数组:
为什么要使用到同向双指针呢?
为了找到最大的连续1的子数组,需要让right指针去不停地向右扩张
为了保证在这个子数组中0的个数不超过k个:
所以当right指针不停地向右扩张,导致子数组中0的个数超过了k个的时候,那就需要让left指针将多于的0去除掉,这就是同向双指针动态调整子数组(窗口)的过程,也就称之为滑动窗口。
第三步:想要解决这类问题,切入点啥,即第一步我们要从哪里开始?
先去判定这道题目是否符合滑动窗口的要求,发现这道题完全符合,那就开始使用for循环来通过同向双指针去动态调整子数组了
第四步:解决这个问题的流程是怎么样的?这里说的流程是指,这道题可以分成12345…步,只要按照12345这个顺序做下去,我们就能解决这个问题。
- 判定题目要求是否是连续最长/最短子数组,是否需要动态调整子数组
- 是的话,定义int变量ret为最终结果(最大的连续1的个数),同时使用for循环,一个int的zero变量来计算子数组中0的个数,让right指针向右扩张,遇到0,那就将zero加一。
- 如果在扩张的过程中,需要不停地判断0的个数zero是不是大于了k
- 如果判定0的个数大于了k,那就去判断left指针位置处的值是不是0,如果是0,那么就让zero减一,同时让left向右移动一位,如果下一个left位置依旧是0,那就继续让zero减一,同时left继续向右移动一位,知道zero不超过k为止
- 经过上面的循环之后,此时left和right之间的子数组就是满足条件的子数组(连续为1,0的个数不超过k个)
- 此时就更新最终结果ret就是(right-left+1):更新结果
第五步:在流程的12345步中,每一步的目标是什么(就是要求到些什么)?每一步需要用到哪些知识
- 判定是不是滑动窗口的题目,是不是求连续的子数组
- 在for循环中,通过ret变量来计算出最终的结果,通过zero变量来计算子数组中的0的个数,通过zero判定是否要进入窗口和出窗口,当right向右移动,tight的位置出现了0,那就zero+1
- 在for循环中right不停地向右走时,zero+1,要使用while循环去不停地判断zero是不是超过了k,当zero超过了k,此时说明子数组中0超过了k个,就需要减少0的个数,就是让left也开始向右移动,当left的位置也是0的时候,就zero减一,同时left++一次,就代表子数组减少了一个0,此时也就是需要不停地判断zero是不是还是大于k的,是,那么就继续上述操作,不是,那就跳出这个判断的while循环(出窗口),然后去进行结果的更新
第六步:要思考在运用这些知识和技巧的时候,有些需要注意的地方。
- 这个题目是求的是最长的连续子数组
- 使用的是同向双指针的方式
- 使用while循环来不停地判断是否要进行出窗口的操作
- 在出窗口之后,满足了条件之后层,才要去跟新结果
第七步:要解决这个问题,最终的目标是啥?也就是说,我最终要求出的是啥?
最终的目标是为了去求出一个长度,这个数组中连续为1的最大的的子数组的长度
第八步:重新对上面的第一步至第七步,进行回顾和揣摩(包括问题类型,特征,切入点,解决过程,1234567…步,每步需要用的哪些知识方法)
(2)AI+8步归纳结合
在模仿完题目后,先自己用8步归纳法对错题进行归纳,然后让deepseek,按照8步归纳法的原则,对题目进行总结归纳,你再把自己总结归纳出的东西,跟AI对比,看看有哪些地方可以改进。
上面是我自己根据这八个步骤去总结归纳的解题流程,下面是根据DeepSeek归纳总结出来的流程:
AI的解题思路
第一步:问题描述:用自己的话,描述一下眼前的这个问题,它是一个怎么样的问题
问题本质:
在二进制数组中,允许最多翻转k个0为1的情况下,找到最长的连续1的子数组的长度。
第二步:问题特征:这个问题有哪些特征,能让我们去判断,它属于这一类的问题?
- 连续子数组:结果必须是连续的元素
- 最值问题:要求最大长度
- 动态调整:窗口内0的数量受k限制,需动态调整窗口大小
- 滑动窗口适用:符合“连续子数组 + 最值”的经典特征
第三步:解题切入点:想要解决这类问题,切入点啥,即第一步我们要从哪里开始?
- 判断是否属于滑动窗口问题(连续子数组 + 最值)
- 定义窗口状态变量
zero
(记录窗口内0的数量) - 用双指针动态维护窗口的合法性(
zero ≤ k
)
第四步:解题流程:解决这个问题的流程是怎么样的?这里说的流程是指,这道题可以分成12345…步,只要按照12345这个顺序做下去,我们就能解决这个问题。
-
初始化:
left = 0
(左指针)zero = 0
(窗口内0的数量)ret = 0
(最终结果)
-
右指针扩张:
right
从0开始遍历数组- 遇到
nums[right] == 0
时,zero++
-
左指针收缩:
- 当
zero > k
时,移动left
直到zero ≤ k
:- 若
nums[left] == 0
,则zero--
left++
- 若
- 当
-
更新结果:
- 计算当前窗口长度
right - left + 1
- 更新
ret = max(ret, 当前长度)
- 计算当前窗口长度
第五步:每步目标与知识点:在流程的12345步中,每一步的目标是什么(就是要求到些什么)?每一步需要用到哪些知识
步骤 | 目标 | 关键知识点 |
---|---|---|
初始化 | 定义初始状态 | 变量初始化 |
右指针扩张 | 探索新元素 | 遍历数组 |
左指针收缩 | 保证窗口合法性 | 条件判断与指针移动 |
更新结果 | 记录最大值 | 数学比较操作 |
第六步:注意事项:要思考在运用这些知识和技巧的时候,有些需要注意的地方。
- 更新时机:在左指针收缩后更新结果(因为是求最大窗口)
- 边界处理:当
k >= 总0数
时,直接返回数组长度 - 效率优化:每个元素最多被访问两次,时间复杂度O(n)
第七步:最终目标:要解决这个问题,最终的目标是啥?也就是说,我最终要求出的是啥?
求出允许翻转k个0时,数组中连续1的最长子数组长度。
第八步:整体回顾
- 问题类型:滑动窗口(最大窗口问题)
- 核心特征:连续子数组、动态调整、最值
- 解决流程:
- 右指针扩张探索
- 左指针收缩保证合法性
- 收缩后更新结果
- 关键点:窗口状态的维护(
zero
计数器)
最终代码实现
class Solution {public int longestOnes(int[] nums, int k) {int n = nums.length;int zero = 0; // 窗口内0的数量int ret = 0; // 最终结果int left = 0; // 左指针for (int right = 0; right < n; right++) {if (nums[right] == 0) zero++; // 右指针扩张,更新0的数量// 当0的数量超过k时,收缩左指针while (zero > k) { if (nums[left] == 0) zero--; // 移出的是0,则减少计数left++; }// 更新最大长度(收缩后窗口一定合法)ret = Math.max(ret, right - left + 1); }return ret;}
}
关键逻辑注释
- 右指针扩张:探索新元素,统计0的数量。
- 左指针收缩:当0的数量超过k时,逐步移出窗口左侧元素,直到合法。
- 更新结果:在每次窗口调整后,计算当前合法窗口的长度并更新最大值。