最多翻转 K 个 0 后的最长连续 1 怎么用滑动窗口?
简化版
把“最多翻转 K 个 0”理解成窗口里最多允许有 K 个 0。
右指针扩张窗口并统计 0 的数量;如果 0 的数量超过 K,就移动左指针直到窗口重新合法。
每次窗口合法时,用 right - left + 1 更新最长长度。
详细版
这题是典型的“最长合法窗口”。
维护窗口 [left, right] 中 0 的个数 zeroCount。遍历 right,遇到 0 就加一;如果 zeroCount > k,说明翻转次数不够,需要不断移动 left,并在移出 0 时减少计数。
当窗口重新满足 zeroCount <= k 后,它表示一个可以通过翻转最多 k 个 0 变成全 1 的连续区间。
每轮更新答案即可。
时间复杂度 O(n),空间复杂度 O(1)。
完整版教学
一、题目条件怎么翻译成窗口约束
“最多翻转 K 个 0”不是让你真的修改数组,而是要求找到一个连续区间,区间里 0 的数量不超过 K。因为只要 0 的数量不超过 K,就能把这些 0 全部翻成 1。于是问题变成:找最长的连续子数组,使得其中 0 的数量 <= K。
记忆钩子:翻转 K 个 0,本质是窗口内最多容纳 K 个坏字符。
二、为什么适合滑动窗口
当右指针向右扩张时,窗口里的 0 数量可能增加,约束可能被破坏。当左指针向右收缩时,窗口里的 0 数量可能减少,约束会变得更容易满足。这种“右扩可能变坏,左缩恢复合法”的结构,就是滑动窗口的经典形态。
| 操作 | zeroCount 变化 | 窗口状态 |
|---|---|---|
| 右端加入 1 | 不变 | 不会更差 |
| 右端加入 0 | 加 1 | 可能超限 |
| 左端移出 0 | 减 1 | 可能恢复合法 |
窗口始终向右移动,不需要回头。
三、带数字推演
数组 [1,1,0,0,1,1,1,0],K = 1。
right 到第 2 位 0:窗口 [1,1,0],zero=1,长度 3
right 到第 3 位 0:zero=2 超限,left 右移直到移出第一个 0
窗口变成 [0],zero=1
right 继续扩到 [0,1,1,1],长度 4
最长长度是 4,对应翻转一个 0 后得到连续 1。
四、代码模板
实现如下:
function longestOnes(nums, k) {
let left = 0
let zeroCount = 0
let ans = 0
for (let right = 0; right < nums.length; right++) {
if (nums[right] === 0) zeroCount++
while (zeroCount > k) {
if (nums[left] === 0) zeroCount--
left++
}
ans = Math.max(ans, right - left + 1)
}
return ans
}
这里用 while 比 if 更稳,因为一次右扩可能让窗口超限,需要持续恢复到合法状态。
五、和“无重复字符最长子串”的关系
这题和无重复字符最长子串都属于最长窗口,但约束不同。无重复字符维护的是每个字符最多出现 1 次;本题维护的是 0 最多出现 K 次。模板相同,差别在于窗口状态变量不同。
右扩:加入新元素,更新状态
非法:左缩,恢复状态
合法:更新最大长度
掌握这个抽象后,很多最长子数组题都能统一处理。
六、常见误区与追问
- 误区:真的去翻转数组。 题目只问最长长度,统计窗口内 0 的数量即可。
- 误区:窗口超限后只移动一次 left。 左端移出的是 1 时,zeroCount 不变,窗口仍可能非法。
- 误区:更新答案放在窗口非法时。 答案必须来自合法窗口。
- 追问:K 等于 0 怎么办? 算法仍然成立,此时就是找最长连续 1。
- 追问:为什么复杂度是 O(n)? 两个指针都只向右走,每个元素最多进入和离开窗口一次。
这些点说明滑动窗口的本质是维护合法区间,而不是模拟操作。
七、加强记忆
这题记成“窗口里最多 K 个 0”。右指针负责扩张候选答案,zeroCount 记录需要翻转的 0 数量;一旦超过 K,左指针持续右移,直到 0 数量重新合规。每次合法后更新最大长度。这个模型还能迁移到“最多替换 K 个字符”“最多删除一个元素”等窗口约束题。